diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..be88519961 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @oapi-codegen/maintainers diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..cf30ec2c3c --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,5 @@ +github: +- oapi-codegen +- jamietanna +tidelift: go/github.com/oapi-codegen/oapi-codegen/v2 +open_collective: oapi-codegen diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 61bacceb00..0000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,7 +0,0 @@ -# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates -version: 2 -updates: - - package-ecosystem: "gomod" - directory: "/" - schedule: - interval: "weekly" diff --git a/.github/label-actions.yml b/.github/label-actions.yml new file mode 100644 index 0000000000..342da5bb30 --- /dev/null +++ b/.github/label-actions.yml @@ -0,0 +1,49 @@ +'auto:no-head-branch': + comment: > + Thanks for the contribution! + + It looks like this PR was raised from the `main` branch. + + As per [our contributing guidelines](https://github.com/oapi-codegen/oapi-codegen/blob/main/CONTRIBUTING.md#before-you-raise-a-pr), this can make it difficult for us to push changes to your fork - for instance to directly push a change to address a comment that we would raise, or to finalise changes before merge. + + Please re-raise the PR from a branch on your repository that isn't `main` / any protected branches. + + Thank you in advance 🙇🏼 + +'auto:no-mentions': + comment: > + Thanks for your use of `oapi-codegen`! + + We are very appreciative of your interest in improving the project, and that you're engaged with us. + + However, @-mentioning the maintainers will be very unlikely to help move your issue along, and we do not want to encourage behaviour that leads to the "noisiest" users getting the benefit over folks who are waiting their turn. + + Please remember that `oapi-codegen` is for the most part run by volunteers, and [we have a request out](https://github.com/oapi-codegen/oapi-codegen/discussions/1606) to companies who use the project to help make it more sustainable, with sponsorship. + + It will only be that sustained financial support will be able to make it possible to work more efficiently towards user requests. + + For more info on engaging with the maintainers, see [our CONTRIBUTING guide](https://github.com/oapi-codegen/oapi-codegen/blob/main/CONTRIBUTING.md#should-i--mention-the-maintainers-on-an-issue). + +'auto:pinning-until-release': + comment: > + [Until there is a defined release cadence planned](https://github.com/oapi-codegen/oapi-codegen/issues/1519), our [official recommendation](https://github.com/oapi-codegen/oapi-codegen/blob/main/README.md#pinning-to-commits) is that you pin to the latest commit on `main`, which will allow you to pull in this change. + +'auto:sponsorship': + comment: > + Please remember that `oapi-codegen` is for the most part run by volunteers, and [we have a request out](https://github.com/oapi-codegen/oapi-codegen/discussions/1606) to companies who use the project to help make it more sustainable, with sponsorship. + + If this is high-priority for your organisation, please consider whether you would like to sponsor ongoing maintenance of the project, or that you may want to engage the maintainers to perform a one-off contract/bug bounty. + + Also remember that although it can seem like "just" a "small change", the maintainers are on the hook for maintaining the changes: + + > [No is temporary, yes is forever](https://www.oreilly.com/library/view/hands-on-design-patterns/9781789135565/3f396314-dac8-446c-ab02-768ae91296fa.xhtml) + +'reproduction:needed': + comment: > + Thanks for the report! + + To help maintainers (or other users) help with triaging and fixing of the issue more easily, we need a [Minimal Reproduction](https://github.com/oapi-codegen/oapi-codegen/blob/main/CONTRIBUTING.md#minimal-reproductions) test case. + + Please follow the instructions, and then share a URL to the repository. + + We will not accept `.zip` files as reproductions, sorry! diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000000..792590af6e --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,3 @@ +'auto:no-head-branch': +- head-branch: + - '^main$' diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000000..852c651a59 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1 @@ +_extends: oapi-codegen/.github diff --git a/.github/sponsors/cybozu.svg b/.github/sponsors/cybozu.svg new file mode 100644 index 0000000000..6691fbd916 --- /dev/null +++ b/.github/sponsors/cybozu.svg @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/.github/sponsors/devzero-dark.svg b/.github/sponsors/devzero-dark.svg new file mode 100644 index 0000000000..9085ad1802 --- /dev/null +++ b/.github/sponsors/devzero-dark.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/.github/sponsors/devzero-light.svg b/.github/sponsors/devzero-light.svg new file mode 100644 index 0000000000..8889a29199 --- /dev/null +++ b/.github/sponsors/devzero-light.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/sponsors/livepeer-dark.svg b/.github/sponsors/livepeer-dark.svg new file mode 100644 index 0000000000..2abe96ec95 --- /dev/null +++ b/.github/sponsors/livepeer-dark.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/.github/sponsors/livepeer-light.svg b/.github/sponsors/livepeer-light.svg new file mode 100644 index 0000000000..71b5a40b27 --- /dev/null +++ b/.github/sponsors/livepeer-light.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/.github/sponsors/speakeasy-dark.svg b/.github/sponsors/speakeasy-dark.svg new file mode 100644 index 0000000000..aae8f7a9d9 --- /dev/null +++ b/.github/sponsors/speakeasy-dark.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/sponsors/speakeasy-light.svg b/.github/sponsors/speakeasy-light.svg new file mode 100644 index 0000000000..ab439943db --- /dev/null +++ b/.github/sponsors/speakeasy-light.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46f53f7eaa..2d5e0a1c6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,20 +1,36 @@ -name: Build project -on: [ push, pull_request ] +name: CI +on: + push: {} + pull_request: {} + workflow_dispatch: {} +permissions: + contents: read jobs: build: - name: Build + uses: oapi-codegen/actions/.github/workflows/ci.yml@75566d848d25021f137594c947f26171094fb511 # v0.5.0 + with: + excluding_versions: '["1.22", "1.23"]' + lint_versions: '["1.25"]' + + build-binaries: runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + version: + - "stable" + - "oldstable" steps: - name: Check out source code - uses: actions/checkout@v3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: - go-version-file: 'go.mod' - - - name: Test - run: make test + go-version: ${{ matrix.version }} - name: Build run: go build ./cmd/oapi-codegen + env: + # A combination of our GitHub Actions setup, with the Go toolchain, leads to inconsistencies in calling `go env`, in particular with Go 1.21, where having (the default) `GOTOOLCHAIN=auto` results in build failures + GOTOOLCHAIN: local diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml deleted file mode 100644 index e6d92b67c6..0000000000 --- a/.github/workflows/generate.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Ensure generated files are up-to-date -on: [ push, pull_request ] -jobs: - build: - name: Build - runs-on: ubuntu-latest - steps: - - name: Check out source code - uses: actions/checkout@v3 - - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version-file: 'go.mod' - - - name: Run `make generate` - run: make generate - - - name: Check for no untracked files - run: git status && git diff-index --quiet HEAD -- diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml new file mode 100644 index 0000000000..6be0b7d905 --- /dev/null +++ b/.github/workflows/govulncheck.yml @@ -0,0 +1,39 @@ +name: Determine known CVEs through `govulncheck` +on: + push: + branches: + - main + schedule: + # Mondays at 0000 + - cron: "0 0 * * 1" + +permissions: + contents: read + +jobs: + check-for-vulnerabilities: + name: Check for vulnerabilities using `govulncheck` + runs-on: ubuntu-latest + permissions: + security-events: write + contents: read + steps: + - uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4 + with: + # to be explicit, we're only checking the top-level `oapi-codegen` package + # we are intentionally NOT intending to keep on top of security updates in `internal/test` or `examples`, or any submodules thereof + go-package: ./... + # NOTE that we want to produce the SARIF-formatted report, which can then be consumed by other tools ... + output-format: sarif + output-file: govulncheck.sarif + + # ... such as the Code Scanning tab (https://github.com/oapi-codegen/oapi-codegen/security/code-scanning?query=is%3Aopen+branch%3Amain+tool%3Agovulncheck) + - name: Upload SARIF file + uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 + with: + sarif_file: govulncheck.sarif + category: govulncheck + + - name: Print code scanning results URL + run: | + echo "Results: https://github.com/${{ github.repository }}/security/code-scanning?query=is%3Aopen+branch%3Amain+tool%3Agovulncheck" diff --git a/.github/workflows/label-actions.yml b/.github/workflows/label-actions.yml new file mode 100644 index 0000000000..9e854222e2 --- /dev/null +++ b/.github/workflows/label-actions.yml @@ -0,0 +1,23 @@ +name: 'Label Actions' + +on: + issues: + types: [labeled] + discussion: + types: [labeled] + pull_request_target: + types: [labeled] + +permissions: + contents: read + issues: write + discussions: write + pull-requests: write + +jobs: + reaction: + runs-on: ubuntu-latest + steps: + - uses: dessant/label-actions@9e5fd757ffe1e065abf55e9f74d899dbe012922a # v5.0.0 + with: + github-token: ${{ github.token }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000000..c7eb2865f1 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,15 @@ +name: "Pull Request Labeler" +on: +- pull_request_target + +permissions: + contents: read + +jobs: + labeler: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 6757ff953e..0000000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Lint project -on: [push, pull_request] -jobs: - build: - name: Build - runs-on: ubuntu-latest - steps: - - name: Check out source code - uses: actions/checkout@v3 - - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version-file: 'go.mod' - - - name: golangci-lint - uses: golangci/golangci-lint-action@v3 - with: - # Make sure this matches the version we've got in our `Makefile` - version: v1.50.1 diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 0000000000..7b03a27d7a --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,25 @@ +name: Release Drafter + +on: + push: + branches: + - main + workflow_dispatch: {} + +permissions: + contents: read + +jobs: + update_release_draft: + permissions: + contents: write + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: release-drafter/release-drafter@6db134d15f3909ccc9eefd369f02bd1e9cffdf97 # v6.2.0 + with: + name: next + tag: next + version: next + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000000..459dd315e0 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,46 @@ +name: Scorecard supply-chain security +on: + schedule: + - cron: "0 5 * * 1" + push: + branches: + - main + +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard + security-events: write + # Needed to publish results and get a badge (see publish_results below) + id-token: write + + steps: + - name: "Checkout code" + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + show-progress: false + + - name: "Run analysis" + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + + - name: "Upload artifact" + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 + with: + sarif_file: results.sarif diff --git a/.github/workflows/tidy.yml b/.github/workflows/tidy.yml deleted file mode 100644 index 8863c542f1..0000000000 --- a/.github/workflows/tidy.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Ensure `go mod tidy` has been run -on: [ push, pull_request ] -jobs: - build: - name: Build - runs-on: ubuntu-latest - steps: - - name: Check out source code - uses: actions/checkout@v3 - - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version-file: 'go.mod' - - - name: Install `tidied` - run: go install gitlab.com/jamietanna/tidied@latest - - - name: Check for no untracked files - run: tidied -verbose diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 636eb8fe12..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: go -go: - - 1.16.x -env: - global: - - GO111MODULE: "on" - - CGO_ENABLED: "0" -script: - - make tidy - - make generate - - make test - - git --no-pager diff && [[ 0 -eq $(git status --porcelain | wc -l) ]] -notifications: - email: - on_success: never diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..6c4205ae6c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,98 @@ +## Contributing + +If you're interested in contributing to `oapi-codegen`, the first thing we have to say is thank you! We'd like to extend our gratitude to anyone who takes the time to improve this project. + +`oapi-codegen` is being actively maintained, however the two people who do so are very busy, and can only set aside time for this project every once in a while, so our release cadence is slow and conservative. + +> [!NOTE] +> We're actively considering what needs to change to make `oapi-codegen` more sustainable, and hope that we can share soon some options. + +This guide is a starting point, and we'll absolutely improve it on an ongoing basis. We've managed to go ~4 years without a substantial guide like this - sometimes to the detriment of contributors - and would love to keep improving this guide, and the project, for the best of the community. + +### When may we not change things? + +Generating code which others depend on, which is based on something as complex as OpenAPI is fraught with many edge cases, and we prefer to leave things as they are if there is a reasonable workaround. + +We'll try to avoid adding too much noise into generated code, or introduce breaking changes (as per SemVer). See also "Backwards compatibility" in the README. + +### Raising a bug + +If you believe you have encountered a bug, please raise an issue. + +> [!TIP] +> Please follow the [minimal reproductions](#minimal-reproductions) documentation to improve our ability to support triaging + +This may get converted into a feature request if we don't deem it a bug, but a missing feature. + +### Asking a question + +We'd prefer that questions about "how do I use (this feature)?" or "what do the community think about ...?" get asked using [GitHub Discussions](https://github.com/oapi-codegen/oapi-codegen/discussions) which allow the community to answer them more easily. + +### Making changes that tweak generated code + +If you are making changes to the codebase that affects the code that gets generated, you will need to make sure that you have regenerated any generated test cases in the codebase using `make generate`. + +These generated test cases and examples provide a means to not only validate the functionality, but as they are checked in to source code, allow us to see if there are any subtle issues or breaking changes. + +> [!NOTE] +> Significant changes to generated code are unlikely to be merged, especially in cases where there would be a breaking change that all consumers would have to respond to i.e. renaming a function or changing the function signature. +> +> However, if we can make this an opt-in feature (using the `output-options` configuration object) then that would be our preference. + +### Feature enhancements + +It's great that you would like to improve `oapi-codegen` and add new futures. + +We would prefer there be an issue raised for a feature request first, especially if it may be a duplicate of existing requests. However, sometimes that isn't possible - or takes longer than the code changes required - so it can be excused. + +Features that amend the way existing codegen works should - ideally - be behind an opt-in feature flag using the `output-options` configuration object. + +### Minimal reproductions + +> [!TIP] +> The minimal reproductions for bugs may get taken into the codebase (licensed under `Apache-2.0`) as a test-case for future regression testing +> +> However, this can only be done if you license the code under `Apache-2.0` itself - if you are comfortable doing so, please do. + +When raising a bug report, or asking a question about functionality, it's super helpful if you can share: + +- The version of `oapi-codegen` you're using + - You _may_ get asked to update to a later - or latest - version, to see if the issue persists +- The YAML configuration file you're using +- The OpenAPI spec you're using + - However, we would prefer it only be the _absolute minimum_ specification, to limit the noise while trying to debug the issue, and to reduce information exposure from internal API development +- What problem you're seeing +- What the expected behaviour is +- What version of Go you're using + +> [!CAUTION] +> When sharing a minimal reproduction, please be aware of sharing any internal information about the APIs you're developing, or any sensitive Intellectual Property. + +### Before you raise a PR + +> [!NOTE] +> Please raise PRs from a branch that isn't the `master` or `main` branch on your repo. This generally means that as maintainers, we can't push changes to the branch directly. + +Before you send the PR, please run the following commands locally: + +```sh +make tidy +make test +make generate +make lint +``` + +It is important to use the `make` tasks due to the way we're (ab)using the Go module system to split the project into multiple modules to reduce our dependency bloat in the main module. + +These are also run in GitHub Actions, across a number of Go releases. + +It's recommended to raise a draft PR first, so you can get feedback on the PR from GitHub, and review your own changes, before getting the attention of a maintainer. + +### "Should I @-mention the maintainers on an issue" + +Please try to avoid pinging the maintainers in an issue, Pull Request, or discussion. + +> [!NOTE] +> We're actively considering what needs to change to make `oapi-codegen` more sustainable, and hope that we can share soon some options. + +The project is run on a volunteer basis, and as such, tagging us on issues - especially if you've just raised them - is largely unhelpful. We monitor the issues and work to triage them as best we can with the time we have allocated for it. diff --git a/Makefile b/Makefile index f76755631c..f09a336c11 100644 --- a/Makefile +++ b/Makefile @@ -6,24 +6,47 @@ help: @echo "Targets:" @echo " generate: regenerate all generated files" @echo " test: run all tests" - @echo " gin_example generate gin example server code" @echo " tidy tidy go mod" + @echo " lint lint the project" $(GOBIN)/golangci-lint: - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) v1.50.1 + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) v2.10.1 .PHONY: tools tools: $(GOBIN)/golangci-lint lint: tools + # run the root module explicitly, to prevent recursive calls by re-invoking `make ...` top-level $(GOBIN)/golangci-lint run ./... + # then, for all child modules, use a module-managed `Makefile` + git ls-files '**/*go.mod' -z | xargs -0 -I{} bash -xc 'cd $$(dirname {}) && env GOBIN=$(GOBIN) make lint' + +lint-ci: tools + # for the root module, explicitly run the step, to prevent recursive calls + $(GOBIN)/golangci-lint run ./... --output.text.path=stdout --timeout=5m + # then, for all child modules, use a module-managed `Makefile` + git ls-files '**/*go.mod' -z | xargs -0 -I{} bash -xc 'cd $$(dirname {}) && env GOBIN=$(GOBIN) make lint-ci' generate: + # for the root module, explicitly run the step, to prevent recursive calls go generate ./... + # then, for all child modules, use a module-managed `Makefile` + git ls-files '**/*go.mod' -z | xargs -0 -I{} bash -xc 'cd $$(dirname {}) && make generate' test: + # for the root module, explicitly run the step, to prevent recursive calls go test -cover ./... + # then, for all child modules, use a module-managed `Makefile` + git ls-files '**/*go.mod' -z | xargs -0 -I{} bash -xc 'cd $$(dirname {}) && make test' tidy: - @echo "tidy..." + # for the root module, explicitly run the step, to prevent recursive calls go mod tidy + # then, for all child modules, use a module-managed `Makefile` + git ls-files '**/*go.mod' -z | xargs -0 -I{} bash -xc 'cd $$(dirname {}) && make tidy' + +tidy-ci: + # for the root module, explicitly run the step, to prevent recursive calls + tidied -verbose + # then, for all child modules, use a module-managed `Makefile` + git ls-files '**/*go.mod' -z | xargs -0 -I{} bash -xc 'cd $$(dirname {}) && make tidy-ci' diff --git a/README.md b/README.md index c916753cdb..aa4dc860ee 100644 --- a/README.md +++ b/README.md @@ -1,791 +1,4498 @@ -OpenAPI Client and Server Code Generator ----------------------------------------- +# `oapi-codegen` -⚠️ This README may be for the latest development version, which may contain -unreleased changes. Please ensure you're looking at the README for the latest -release version. +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9450/badge)](https://www.bestpractices.dev/projects/9450) -This package contains a set of utilities for generating Go boilerplate code for -services based on -[OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md) -API definitions. When working with services, it's important to have an API -contract which servers and clients both implement to minimize the chances of -incompatibilities. It's tedious to generate Go models which precisely correspond to -OpenAPI specifications, so let our code generator do that work for you, so that -you can focus on implementing the business logic for your service. +`oapi-codegen` is a command-line tool and library to convert OpenAPI specifications to Go code, be it [server-side implementations](#generating-server-side-boilerplate), [API clients](#generating-api-clients), or simply [HTTP models](#generating-api-models). -We have chosen to focus on [Echo](https://github.com/labstack/echo) as -our default HTTP routing engine, due to its speed and simplicity for the generated -stubs, and [Chi](https://github.com/go-chi/chi), and [Gin](https://github.com/gin-gonic/gin) -have also been added by contributors as additional routers. We chose Echo because -the `Context` object is a mockable interface, and it allows for some advanced -testing. +Using `oapi-codegen` allows you to reduce the boilerplate required to create or integrate with services based on [OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md), and instead focus on writing your business logic, and working on the real value-add for your organisation. -This package tries to be too simple rather than too generic, so we've made some -design decisions in favor of simplicity, knowing that we can't generate strongly -typed Go code for all possible OpenAPI Schemas. If there is a way to accomplish -something via utility code or reflection, it's probably a better approach than -code generation, which is fragile due to the very dynamic nature of OpenAPI and -the very static nature of Go. +With `oapi-codegen`, there are a few [Key Design Decisions](#key-design-decisions) we've made, including: -## Overview +- idiomatic Go, where possible +- fairly simple generated code, erring on the side of duplicate code over nicely refactored code +- supporting as much of OpenAPI 3.x as is possible, alongside Go's type system -We're going to use the OpenAPI example of the -[Expanded Petstore](https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/petstore-expanded.yaml) -in the descriptions below, please have a look at it. +`oapi-codegen` is one part of a wider ecosystem, which can be found described in further detail in the [oapi-codegen organisation on GitHub](https://github.com/oapi-codegen). -In order to create a Go server to serve this exact schema, you would have to -write a lot of boilerplate code to perform all the marshalling and unmarshalling -into objects which match the OpenAPI 3.0 definition. The code generator in this -directory does a lot of that for you. You would run it like so: +⚠️ This README may be for the latest development version, which may contain unreleased changes. Please ensure you're looking at the README for the latest release version. - go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@latest - oapi-codegen petstore-expanded.yaml > petstore.gen.go +## Action Required: The repository for this project has changed -Let's go through that `petstore.gen.go` file to show you everything which was -generated. +As announced in [May 2024](https://github.com/oapi-codegen/oapi-codegen/discussions/1605), +we have moved the project from the deepmap organization to our own organization, and you will need to update your +import paths to pull updates past this point. You need to do a recursive search/replace from +`github.com/deepmap/oapi-codegen/v2` to `github.com/oapi-codegen/oapi-codegen/v2`. +> [!IMPORTANT] +> `oapi-codegen` moved to its new home with the version tag `v2.3.0`. -## Generated Server Boilerplate +If you are using `v2.2.0` or below, please install like so: + +```sh +# for the binary install +go install github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen@v2.2.0 +``` + +If you are using `v2.3.0` or above, please install like so, using the new module import path: + +```sh +# for the binary install +go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest +``` + +## Install + +It is recommended to follow [the `go tool` support available from Go 1.24+](https://www.jvt.me/posts/2025/01/27/go-tools-124/) for managing the dependency of `oapi-codegen` alongside your core application. + +To do this, you run `go get -tool`: + +```sh +$ go get -tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest +# this will then modify your `go.mod` +``` + +From there, each invocation of `oapi-codegen` would be used like so: + +```go +//go:generate go tool oapi-codegen -config cfg.yaml ../../api.yaml +``` + +Alternatively, you can install it as a binary with: + +```sh +$ go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest +$ oapi-codegen -version +``` + +Which then means you can invoke it like so: + +```go +//go:generate oapi-codegen --config=config.yaml ../../api.yaml +``` + +Note that you can also [move your `tools.go` into its own sub-module](https://www.jvt.me/posts/2024/09/30/go-tools-module/) to reduce the impact on your top-level `go.mod`. + +### Pinning to commits + +While the project does not ([yet](https://github.com/oapi-codegen/oapi-codegen/issues/1519)) have a defined release cadence, there may be cases where you want to pull in yet-unreleased changes to your codebase. + +Therefore, you may want to pin your dependency on `oapi-codegen` to a given commit hash, rather than a tag. + +This is **officially recommended** for consumers of `oapi-codegen`, who want features/bug fixes that haven't yet been released. + +We aim to keep the default branch ready-to-release so you should be able to safely pin. + +To do so, you can run: + +```sh +# pin to the latest version on the default branch +$ go get github.com/oapi-codegen/oapi-codegen/v2@main +# alternatively, to a commit hash i.e. https://github.com/oapi-codegen/oapi-codegen/commit/71e916c59688a6379b5774dfe5904ec222b9a537 +$ go get github.com/oapi-codegen/oapi-codegen/v2@71e916c59688a6379b5774dfe5904ec222b9a537 +``` + +This will then make a change such as: + +```diff +diff --git go.mod go.mod +index 44f29a4..436a780 100644 +--- go.mod ++++ go.mod +@@ -2,21 +2,20 @@ +-require github.com/oapi-codegen/oapi-codegen/v2 v2.1.0 ++require github.com/oapi-codegen/oapi-codegen/v2 v2.1.1-0.20240331212514-80f0b978ef16 +``` + +## Usage + +`oapi-codegen` is largely configured using a YAML configuration file, to simplify the number of flags that users need to remember, and to make reading the `go:generate` command less daunting. + +For full details of what is supported, it's worth checking out [the GoDoc for `codegen.Configuration`](https://pkg.go.dev/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen#Configuration). + +We also have [a JSON Schema](configuration-schema.json) that can be used by IDEs/editors with the Language Server Protocol (LSP) to perform intelligent suggestions, i.e.: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/v2.6.0/configuration-schema.json +package: api +# ... +``` + +Note that it's recommended to pin to a specific version of the configuration schema, so it matches the version of `oapi-codegen` you're using. For instance, if you're using [Renovate](https://docs.renovatebot.com/), you can [have Renovate automagically update this version for you](https://www.jvt.me/posts/2026/03/01/oapi-codegen-config-renovate/). + +### Backwards compatibility + +Although we strive to retain backwards compatibility - as a project that's using a stable API per SemVer - there are sometimes opportunities we must take to fix a bug that could cause a breaking change for [people relying upon the behaviour](https://xkcd.com/1172/). + +In this case, we will expose a [compatibility option](https://pkg.go.dev/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen#CompatibilityOptions) to restore old behaviour. + +## Features + +At a high level, `oapi-codegen` supports: + +- Generating server-side boilerplate for [a number of servers](#supported-servers) ([docs](#generating-server-side-boilerplate)) +- Generating client API boilerplate ([docs](#generating-api-clients)) +- Generating the types ([docs](#generating-api-models)) +- Splitting large OpenAPI specs across multiple packages([docs](#import-mapping)) + - This is also known as "Import Mapping" or "external references" across our documentation / discussion in GitHub issues + +## What does it look like? + +Below we can see a trimmed down example taken from the OpenAPI Petstore [example](examples/petstore-expanded/stdhttp/api/petstore.gen.go): + +```go +// generated code + +type ServerInterface interface { + // ... + // Returns all pets + // (GET /pets) + FindPets(w http.ResponseWriter, r *http.Request, params FindPetsParams) + // ... +} + +// FindPets operation middleware +func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Request) { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params FindPetsParams + + // ------------- Optional query parameter "tags" ------------- + + err = runtime.BindQueryParameter("form", true, false, "tags", r.URL.Query(), ¶ms.Tags) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "tags", Err: err}) + return + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", r.URL.Query(), ¶ms.Limit) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "limit", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.FindPets(w, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.Handler { + m := options.BaseRouter + + if m == nil { + m = http.NewServeMux() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + m.HandleFunc("GET "+options.BaseURL+"/pets", wrapper.FindPets) + + return m +} +``` + +Then, in your own code, you implement the underlying logic for the `FindPets` implementation: + +```go +type PetStore struct { + Pets map[int64]Pet + NextId int64 + Lock sync.Mutex +} + +// Make sure we conform to ServerInterface + +var _ ServerInterface = (*PetStore)(nil) + +func NewPetStore() *PetStore { + return &PetStore{ + Pets: make(map[int64]Pet), + NextId: 1000, + } +} + +// FindPets implements all the handlers in the ServerInterface +func (p *PetStore) FindPets(w http.ResponseWriter, r *http.Request, params FindPetsParams) { + p.Lock.Lock() + defer p.Lock.Unlock() + + var result []Pet + + for _, pet := range p.Pets { + if params.Tags != nil { + // If we have tags, filter pets by tag + for _, t := range *params.Tags { + if pet.Tag != nil && (*pet.Tag == t) { + result = append(result, pet) + } + } + } else { + // Add all pets if we're not filtering + result = append(result, pet) + } + + if params.Limit != nil { + l := int(*params.Limit) + if len(result) >= l { + // We're at the limit + break + } + } + } + + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(result) +} +``` + +As we can see, `oapi-codegen` simplifies some of the boilerplate by taking parameters out of the request and instead allows us to focus on the implementation. + +You'll note that there's still a bit more marshaling of request/response data, which is further reduced by using the [Strict server](#strict-server) functionality. + + +When using the strict server, you'll have the following generated code: + +```go +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + // ... + // Returns all pets + // (GET /pets) + FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error) + // ... +} + +func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictHTTPServerOptions) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: options} +} + +// FindPets operation middleware +func (sh *strictHandler) FindPets(w http.ResponseWriter, r *http.Request, params FindPetsParams) { + var request FindPetsRequestObject + + request.Params = params + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.FindPets(ctx, request.(FindPetsRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "FindPets") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(FindPetsResponseObject); ok { + if err := validResponse.VisitFindPetsResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} +``` + +Then, in your own code, you implement the underlying logic for the `FindPets` implementation: + +```go +// Make sure we conform to StrictServerInterface + +var _ StrictServerInterface = (*PetStore)(nil) + +func NewPetStore() *PetStore { + return &PetStore{ + Pets: make(map[int64]Pet), + NextId: 1000, + } +} + +// FindPets implements all the handlers in the ServerInterface +func (p *PetStore) FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error) { + p.Lock.Lock() + defer p.Lock.Unlock() + + var result []Pet + + for _, pet := range p.Pets { + if request.Params.Tags != nil { + // If we have tags, filter pets by tag + for _, t := range *request.Params.Tags { + if pet.Tag != nil && (*pet.Tag == t) { + result = append(result, pet) + } + } + } else { + // Add all pets if we're not filtering + result = append(result, pet) + } + + if request.Params.Limit != nil { + l := int(*request.Params.Limit) + if len(result) >= l { + // We're at the limit + break + } + } + } + + return FindPets200JSONResponse(result), nil +} +``` + +We can see that this provides the best means to focus on the implementation of the business logic within the endpoint, rather than (un)marshalling types to and from JSON, or wrangling cookies or headers. + +## Key design decisions + +- Produce an interface that can be satisfied by your implementation, with reduced boilerplate +- Bulk processing and parsing of OpenAPI document in Go +- Resulting output is using Go's `text/template`s, which are user-overridable +- Attempts to produce Idiomatic Go +- Single-file output +- Support multiple OpenAPI files by having a package-per-OpenAPI file +- Support of OpenAPI 3.0 + - OpenAPI 3.1 support is [awaiting upstream support](https://github.com/oapi-codegen/oapi-codegen/issues/373) + However, we do have an experimental version using a different OpenAPI parser which does support 3.1 and 3.2, which + you can play around with in our [experimental repo](https://github.com/oapi-codegen/oapi-codegen-exp/tree/main/experimental) + - Note that this does not include OpenAPI 2.0 (aka Swagger) +- Extract parameters from requests, to reduce work required by your implementation +- Implicit `additionalProperties` are ignored by default ([more details](#additional-properties-additionalproperties)) +- Prune unused types by default + +## Generating server-side boilerplate + +`oapi-codegen` shines by making it fairly straightforward (note that this is a purposeful choice of wording here - we want to avoid words like "easy") to generate the server-side boilerplate for a backend API. + +Below you can find the supported servers, and more information about how to implement a server using them. + +To provide you a fully Test Driven Development style test harness to confirm you are following the specification, you could use a tool such as [openapi.tanna.dev/go/validator](https://openapi.tanna.dev/go/validator/), or craft your own. + +### Supported Servers + +Right now, we support the following servers, and are supportive of adding new servers, too! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Server + +generate flag to enable code generation + +Required Go Version + +Example usage +
+ +[Chi](https://github.com/go-chi/chi) + + +chi-server + +1.22+ + + + +For a Chi server, you will want a configuration file such as: + +```yaml +# yaml-language-server: ... +package: api +generate: + chi-server: true + models: true +output: gen.go +``` + +To implement this, check out [the Chi docs](#impl-chi). + +
+ +[Echo](https://github.com/labstack/echo) + + +echo-server + +1.22+ + + +For an Echo server, you will want a configuration file such as: + +```yaml +# yaml-language-server: ... +package: api +generate: + echo-server: true + models: true +output: gen.go +``` + +To implement this, check out [the Echo docs](#impl-echo). + +
+ +[Fiber](https://github.com/gofiber/fiber) + + +fiber-server + +1.24+ + + +For a Fiber server, you will want a configuration file such as: + +```yaml +# yaml-language-server: ... +package: api +generate: + fiber-server: true + models: true +output: gen.go +``` + +To implement this, check out [the Fiber docs](#impl-fiber). + +
+ +[Gin](https://github.com/gin-gonic/gin) + + +gin-server + +1.22+ + + +For a Gin server, you will want a configuration file such as: + +```yaml +# yaml-language-server: ... +package: api +generate: + gin-server: true + models: true +output: gen.go +``` + +To implement this, check out [the Gin docs](#impl-gin). + +
+ +[gorilla/mux](https://github.com/gorilla/mux) + + +gorilla-server + +1.22+ + + +For a gorilla/mux server, you will want a configuration file such as: + +```yaml +# yaml-language-server: ... +package: api +generate: + gorilla-server: true + models: true +output: gen.go +``` + +To implement this, check out [the gorilla/mux docs](#impl-gorillamux). + +
+ +[Iris](https://github.com/kataras/iris) + + +iris-server + +1.22+ + + +For a Iris server, you will want a configuration file such as: + +```yaml +# yaml-language-server: ... +package: api +generate: + iris-server: true + models: true +output: gen.go +``` + +To implement this, check out [the Iris docs](#impl-iris). + +
+ +[1.22+ `net/http`](https://pkg.go.dev/net/http) + + +std-http-server + +1.22+ + + +To use purely `net/http` (for Go 1.22+), you will want a configuration file such as: + +```yaml +# yaml-language-server: ... +package: api +generate: + std-http-server: true + models: true +output: gen.go +``` + +To implement this, check out [the Go 1.22+ `net/http` docs](#impl-stdhttp). + +
+ +### Go 1.22+ `net/http` + + +As of Go 1.22, enhancements have been made to the routing of the `net/http` package in the standard library, which makes it a great starting point for implementing a server with, before needing to reach for another router or a full framework. + +For instance, let's take this straightforward specification: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: Minimal ping API server +paths: + /ping: + get: + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pong' +components: + schemas: + # base types + Pong: + type: object + required: + - ping + properties: + ping: + type: string + example: pong +``` + +This then generates code such as: + +```go +// Pong defines model for Pong. +type Pong struct { + Ping string `json:"ping"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /ping) + GetPing(w http.ResponseWriter, r *http.Request) +} + +func HandlerFromMux(si ServerInterface, m ServeMux) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{ + BaseRouter: m, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.Handler { + m := options.BaseRouter + + // ... omitted for brevity + + m.HandleFunc("GET "+options.BaseURL+"/ping", wrapper.GetPing) + + return m +} +``` + +To implement this HTTP server, we need to write the following code in our [`api/impl.go`](examples/minimal-server/stdhttp/api/impl.go): + +```go +import ( + "encoding/json" + "net/http" +) + +// optional code omitted + +type Server struct{} + +func NewServer() Server { + return Server{} +} + +// (GET /ping) +func (Server) GetPing(w http.ResponseWriter, r *http.Request) { + resp := Pong{ + Ping: "pong", + } + + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(resp) +} +``` + +Now we've got our implementation, we can then write the following code to wire it up and get a running server: + +```go +import ( + "log" + "net/http" + + "github.com/oapi-codegen/oapi-codegen/v2/examples/minimal-server/stdhttp/api" +) + +func main() { + // create a type that satisfies the `api.ServerInterface`, which contains an implementation of every operation from the generated code + server := api.NewServer() + + r := http.NewServeMux() + + // get an `http.Handler` that we can use + h := api.HandlerFromMux(server, r) + + s := &http.Server{ + Handler: h, + Addr: "0.0.0.0:8080", + } + + // And we serve HTTP until the world ends. + log.Fatal(s.ListenAndServe()) +} +``` + +> [!NOTE] +> This doesn't include [validation of incoming requests](#requestresponse-validation-middleware). + +> [!NOTE] +> If you feel like you've done everything right, but are still receiving `404 page not found` errors, make sure that you've got the `go` directive in your `go.mod` updated to: + +```go.mod +go 1.22 +``` + + + +### Chi + + +For instance, let's take this straightforward specification: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: Minimal ping API server +paths: + /ping: + get: + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pong' +components: + schemas: + # base types + Pong: + type: object + required: + - ping + properties: + ping: + type: string + example: pong +``` + +This then generates code such as: + +```go +// Pong defines model for Pong. +type Pong struct { + Ping string `json:"ping"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /ping) + GetPing(w http.ResponseWriter, r *http.Request) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r *mux.Router) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { + r := options.BaseRouter + + // ... + + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/ping", wrapper.GetPing) + }) + + return r +} +``` + +To implement this HTTP server, we need to write the following code in our [`api/impl.go`](examples/minimal-server/chi/api/impl.go): + +```go +import ( + "encoding/json" + "net/http" +) + +// optional code omitted + +type Server struct{} + +func NewServer() Server { + return Server{} +} + +// (GET /ping) +func (Server) GetPing(w http.ResponseWriter, r *http.Request) { + resp := Pong{ + Ping: "pong", + } + + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(resp) +} +``` + +Now we've got our implementation, we can then write the following code to wire it up and get a running server: + +```go +import ( + "log" + "net/http" + + "github.com/oapi-codegen/oapi-codegen/v2/examples/minimal-server/chi/api" + "github.com/go-chi/chi/v5" +) + +func main() { + // create a type that satisfies the `api.ServerInterface`, which contains an implementation of every operation from the generated code + server := api.NewServer() + + r := chi.NewMux() + + // get an `http.Handler` that we can use + h := api.HandlerFromMux(server, r) + + s := &http.Server{ + Handler: h, + Addr: "0.0.0.0:8080", + } + + // And we serve HTTP until the world ends. + log.Fatal(s.ListenAndServe()) +} +``` + +> [!NOTE] +> This doesn't include [validation of incoming requests](#requestresponse-validation-middleware). + +### gorilla/mux + + +For instance, let's take this straightforward specification: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: Minimal ping API server +paths: + /ping: + get: + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pong' +components: + schemas: + # base types + Pong: + type: object + required: + - ping + properties: + ping: + type: string + example: pong +``` + +This then generates code such as: + +```go +// Pong defines model for Pong. +type Pong struct { + Ping string `json:"ping"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /ping) + GetPing(w http.ResponseWriter, r *http.Request) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r *mux.Router) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options GorillaServerOptions) http.Handler { + r := options.BaseRouter + + // ... + + r.HandleFunc(options.BaseURL+"/ping", wrapper.GetPing).Methods("GET") + + return r +} +``` + +To implement this HTTP server, we need to write the following code in our [`api/impl.go`](examples/minimal-server/gorillamux/api/impl.go): + +```go +import ( + "encoding/json" + "net/http" +) + +// optional code omitted + +type Server struct{} + +func NewServer() Server { + return Server{} +} + +// (GET /ping) +func (Server) GetPing(w http.ResponseWriter, r *http.Request) { + resp := Pong{ + Ping: "pong", + } + + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(resp) +} +``` + +Now we've got our implementation, we can then write the following code to wire it up and get a running server: + +```go +import ( + "log" + "net/http" + + "github.com/oapi-codegen/oapi-codegen/v2/examples/minimal-server/gorillamux/api" + "github.com/gorilla/mux" +) + +func main() { + // create a type that satisfies the `api.ServerInterface`, which contains an implementation of every operation from the generated code + server := api.NewServer() + + r := mux.NewRouter() + + // get an `http.Handler` that we can use + h := api.HandlerFromMux(server, r) + + s := &http.Server{ + Handler: h, + Addr: "0.0.0.0:8080", + } + + // And we serve HTTP until the world ends. + log.Fatal(s.ListenAndServe()) +} +``` + +> [!NOTE] +> This doesn't include [validation of incoming requests](#requestresponse-validation-middleware). + +### Echo server + + +For instance, let's take this straightforward specification: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: Minimal ping API server +paths: + /ping: + get: + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pong' +components: + schemas: + # base types + Pong: + type: object + required: + - ping + properties: + ping: + type: string + example: pong +``` + +This then generates code such as: + +```go +// Pong defines model for Pong. +type Pong struct { + Ping string `json:"ping"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /ping) + GetPing(ctx echo.Context) error +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + // ... + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + // ... +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + // ... + + router.GET(baseURL+"/ping", wrapper.GetPing) + +} +``` + +To implement this HTTP server, we need to write the following code in our [`api/impl.go`](examples/minimal-server/echo/api/impl.go): + +```go +import ( + "net/http" + + "github.com/labstack/echo/v4" +) + +// optional code omitted + +type Server struct{} + +func NewServer() Server { + return Server{} +} + +// (GET /ping) +func (Server) GetPing(ctx echo.Context) error { + resp := Pong{ + Ping: "pong", + } + + return ctx.JSON(http.StatusOK, resp) +} +``` + +Now we've got our implementation, we can then write the following code to wire it up and get a running server: + +```go +import ( + "log" + + "github.com/oapi-codegen/oapi-codegen/v2/examples/minimal-server/echo/api" + "github.com/labstack/echo/v4" +) + +func main() { + // create a type that satisfies the `api.ServerInterface`, which contains an implementation of every operation from the generated code + server := api.NewServer() + + e := echo.New() + + api.RegisterHandlers(e, server) + + // And we serve HTTP until the world ends. + log.Fatal(e.Start("0.0.0.0:8080")) +} +``` + +> [!NOTE] +> This doesn't include [validation of incoming requests](#requestresponse-validation-middleware). + +### Fiber server + + +For instance, let's take this straightforward specification: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: Minimal ping API server +paths: + /ping: + get: + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pong' +components: + schemas: + # base types + Pong: + type: object + required: + - ping + properties: + ping: + type: string + example: pong +``` + +This then generates code such as: + +```go +// Pong defines model for Pong. +type Pong struct { + Ping string `json:"ping"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /ping) + GetPing(c *fiber.Ctx) error +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router fiber.Router, si ServerInterface) { + RegisterHandlersWithOptions(router, si, FiberServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router fiber.Router, si ServerInterface, options FiberServerOptions) { + // ... + + router.Get(options.BaseURL+"/ping", wrapper.GetPing) +} +``` + +To implement this HTTP server, we need to write the following code in our [`api/impl.go`](examples/minimal-server/fiber/api/impl.go): + +```go +import ( + "net/http" + + "github.com/gofiber/fiber/v2" +) + +// ensure that we've conformed to the `ServerInterface` with a compile-time check +var _ ServerInterface = (*Server)(nil) + +type Server struct{} + +func NewServer() Server { + return Server{} +} + +// (GET /ping) +func (Server) GetPing(ctx *fiber.Ctx) error { + resp := Pong{ + Ping: "pong", + } + + return ctx. + Status(http.StatusOK). + JSON(resp) +} +``` + +Now we've got our implementation, we can then write the following code to wire it up and get a running server: + +```go +import ( + "log" + + "github.com/oapi-codegen/oapi-codegen/v2/examples/minimal-server/fiber/api" + "github.com/gofiber/fiber/v2" +) + +func main() { + // create a type that satisfies the `api.ServerInterface`, which contains an implementation of every operation from the generated code + server := api.NewServer() + + app := fiber.New() + + api.RegisterHandlers(app, server) + + // And we serve HTTP until the world ends. + log.Fatal(app.Listen("0.0.0.0:8080")) +} +``` + +> [!NOTE] +> This doesn't include [validation of incoming requests](#requestresponse-validation-middleware). + +### Gin server + + +For instance, let's take this straightforward specification: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: Minimal ping API server +paths: + /ping: + get: + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pong' +components: + schemas: + # base types + Pong: + type: object + required: + - ping + properties: + ping: + type: string + example: pong +``` + +This then generates code such as: + +```go +// Pong defines model for Pong. +type Pong struct { + Ping string `json:"ping"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /ping) + GetPing(c *gin.Context) +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router gin.IRouter, si ServerInterface) { + RegisterHandlersWithOptions(router, si, GinServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options GinServerOptions) { + // ... + + router.GET(options.BaseURL+"/ping", wrapper.GetPing) +} +``` + +To implement this HTTP server, we need to write the following code in our [`api/impl.go`](examples/minimal-server/gorillamux/api/impl.go): + +```go +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +// optional code omitted + +type Server struct{} + +func NewServer() Server { + return Server{} +} + +// (GET /ping) +func (Server) GetPing(ctx *gin.Context) { + resp := Pong{ + Ping: "pong", + } + + ctx.JSON(http.StatusOK, resp) +} +``` + +Now we've got our implementation, we can then write the following code to wire it up and get a running server: + +```go +import ( + "log" + "net/http" + + "github.com/oapi-codegen/oapi-codegen/v2/examples/minimal-server/gin/api" + "github.com/gin-gonic/gin" +) + +func main() { + // create a type that satisfies the `api.ServerInterface`, which contains an implementation of every operation from the generated code + server := api.NewServer() + + r := gin.Default() + + api.RegisterHandlers(r, server) + + // And we serve HTTP until the world ends. + + s := &http.Server{ + Handler: r, + Addr: "0.0.0.0:8080", + } + + // And we serve HTTP until the world ends. + log.Fatal(s.ListenAndServe()) +} +``` + +> [!NOTE] +> This doesn't include [validation of incoming requests](#requestresponse-validation-middleware). + +### Iris server + + +For instance, let's take this straightforward specification: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: Minimal ping API server +paths: + /ping: + get: + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pong' +components: + schemas: + # base types + Pong: + type: object + required: + - ping + properties: + ping: + type: string + example: pong +``` + +This then generates code such as: + +```go +// Pong defines model for Pong. +type Pong struct { + Ping string `json:"ping"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /ping) + GetPing(ctx iris.Context) +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router *iris.Application, si ServerInterface) { + RegisterHandlersWithOptions(router, si, IrisServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router *iris.Application, si ServerInterface, options IrisServerOptions) { + // ... + + router.Get(options.BaseURL+"/ping", wrapper.GetPing) + + router.Build() +} +``` + +To implement this HTTP server, we need to write the following code in our [`api/impl.go`](examples/minimal-server/gorillamux/api/impl.go): + +```go +import ( + "net/http" + + "github.com/kataras/iris/v12" +) + +// optional code omitted + +type Server struct{} + +func NewServer() Server { + return Server{} +} + +// (GET /ping) +func (Server) GetPing(ctx iris.Context) { + resp := Pong{ + Ping: "pong", + } + + ctx.StatusCode(http.StatusOK) + _ = ctx.JSON(resp) +} +``` + +Now we've got our implementation, we can then write the following code to wire it up and get a running server: + +```go +import ( + "log" + + "github.com/oapi-codegen/oapi-codegen/v2/examples/minimal-server/iris/api" + "github.com/kataras/iris/v12" +) + +func main() { + // create a type that satisfies the `api.ServerInterface`, which contains an implementation of every operation from the generated code + server := api.NewServer() + + i := iris.Default() + + api.RegisterHandlers(i, server) + + // And we serve HTTP until the world ends. + log.Fatal(i.Listen("0.0.0.0:8080")) +} +``` + +> [!NOTE] +> This doesn't include [validation of incoming requests](#requestresponse-validation-middleware). + +### Strict server + +`oapi-codegen` also supports generating a server that is much more strict with the contract that the implementer requires, and takes inspiration from server-side code generation for RPC servers. + +This takes the boilerplate reduction from the non-strict servers and adds additional boilerplate reduction, allowing you to make the following changes to your function signatures: + +```diff +-FindPets(w http.ResponseWriter, r *http.Request, params FindPetsParams) ++FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error) +``` + +This is the highest level of strictness that `oapi-codegen` supports right now, and it's a good idea to start with this if you want the most guardrails to simplify developing your APIs. + +The strict server has support for: + +- multiple request/response media types and status codes on a given operation +- first-class support for `multipart/form-data` and `application/x-www-form-urlencoded` requests +- returning an [HTTP 500 Internal Server Error](https://http.cat/500), when an `error` is returned from a function +- automagic (un)marshalling of request/responses, and setting `content-type` and HTTP status codes on responses +- binding request values to a struct, a `multipart.Reader` or providing a `io.Reader` + +You can see a little more detail of the generated code in the ["What does it look like"](#what-does-it-look-like-strict) section. + +> [!NOTE] +> To configure the strict server generation, you must specify another server to be generated. For instance: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json +package: api +generate: + # NOTE another server must be added! + chi-server: true + strict-server: true +output: server.gen.go +``` + +> [!NOTE] +> This doesn't include [validation of incoming requests](#requestresponse-validation-middleware). + +## Generating API clients + +As well as generating the server-side boilerplate, `oapi-codegen` can also generate API clients. + +This aims to be an API client that can be used to interact with the methods of the API, and is perfectly suited for production usage. + +However, if you were looking for a slightly more SDK-style approach, or a mix of generated tests and/or documentation, this API client may not be for you, and you may want to look at alternate tooling. + +For instance, given an `api.yaml`: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: Generate models +paths: + /client: + get: + operationId: getClient + responses: + 200: + content: + application/json: + schema: + $ref: "#/components/schemas/ClientType" + put: + operationId: updateClient + responses: + 400: + content: + application/json: + schema: + type: object + properties: + code: + type: string + required: + - code +components: + schemas: + ClientType: + type: object + required: + - name + properties: + name: + type: string + # NOTE that this is not generated by default because it's not referenced. If you want it, you need to use the following YAML configuration: + # + # output-options: + # skip-prune: true + Unreferenced: + type: object + required: + - id + properties: + id: + type: integer +``` + +And a `cfg.yaml`: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json +package: client +output: client.gen.go +generate: + models: true + client: true +``` + +And a `generate.go`: + +```go +package client + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml +``` + +This would then generate: + +```go +package client + +// ... + +// ClientType defines model for ClientType. +type ClientType struct { + Name string `json:"name"` +} + +// ... + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ... + +// The interface specification for the client above. +type ClientInterface interface { + // GetClient request + GetClient(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UpdateClient request + UpdateClient(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +// ... + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetClientWithResponse request + GetClientWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetClientResponse, error) + + // UpdateClientWithResponse request + UpdateClientWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UpdateClientResponse, error) +} + +type GetClientResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *ClientType +} + +// ... +``` + +With this generated client, it is then possible to construct and utilise the client, for instance: + +```go +package client_test + +import ( + "context" + "fmt" + "log" + "net/http" + + "github.com/oapi-codegen/oapi-codegen/v2/examples/client" +) + +func TestClient_canCall() { + // custom HTTP client + hc := http.Client{} + + // with a raw http.Response + { + c, err := client.NewClient("http://localhost:1234", client.WithHTTPClient(&hc)) + if err != nil { + log.Fatal(err) + } + + resp, err := c.GetClient(context.TODO()) + if err != nil { + log.Fatal(err) + } + if resp.StatusCode != http.StatusOK { + log.Fatalf("Expected HTTP 200 but received %d", resp.StatusCode) + } + } + + // or to get a struct with the parsed response body + { + c, err := client.NewClientWithResponses("http://localhost:1234", client.WithHTTPClient(&hc)) + if err != nil { + log.Fatal(err) + } + + resp, err := c.GetClientWithResponse(context.TODO()) + if err != nil { + log.Fatal(err) + } + if resp.StatusCode() != http.StatusOK { + log.Fatalf("Expected HTTP 200 but received %d", resp.StatusCode()) + } + + fmt.Printf("resp.JSON200: %v\n", resp.JSON200) + } + +} +``` + +### With Server URLs + +An OpenAPI specification makes it possible to denote Servers that a client can interact with, such as: + +```yaml +servers: +- url: https://development.gigantic-server.com/v1 + description: Development server +- url: https://{username}.gigantic-server.com:{port}/{basePath} + description: The production API server + variables: + username: + # note! no enum here means it is an open value + default: demo + description: this value is assigned by the service provider, in this example `gigantic-server.com` + port: + enum: + - '8443' + - '443' + default: '8443' + basePath: + # open meaning there is the opportunity to use special base paths as assigned by the provider, default is `v2` + default: v2 +``` + +It is possible to opt-in to the generation of these Server URLs with the following configuration: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json +package: serverurls +output: gen.go +generate: + # NOTE that this uses default settings - if you want to use initialisms to generate i.e. `ServerURLDevelopmentServer`, you should look up the `output-options.name-normalizer` configuration + server-urls: true +``` + +This will then generate the following boilerplate: + +```go +// (the below does not include comments that are generated) + +const ServerUrlDevelopmentServer = "https://development.gigantic-server.com/v1" + +type ServerUrlTheProductionAPIServerBasePathVariable string +const ServerUrlTheProductionAPIServerBasePathVariableDefault = "v2" + +type ServerUrlTheProductionAPIServerPortVariable string +const ServerUrlTheProductionAPIServerPortVariable8443 ServerUrlTheProductionAPIServerPortVariable = "8443" +const ServerUrlTheProductionAPIServerPortVariable443 ServerUrlTheProductionAPIServerPortVariable = "443" +const ServerUrlTheProductionAPIServerPortVariableDefault ServerUrlTheProductionAPIServerPortVariable = ServerUrlTheProductionAPIServerPortVariable8443 + +type ServerUrlTheProductionAPIServerUsernameVariable string +const ServerUrlTheProductionAPIServerUsernameVariableDefault = "demo" + +func ServerUrlTheProductionAPIServer(basePath ServerUrlTheProductionAPIServerBasePathVariable, port ServerUrlTheProductionAPIServerPortVariable, username ServerUrlTheProductionAPIServerUsernameVariable) (string, error) { + // ... +} +``` + +Notice that for URLs that are not templated, a simple `const` definition is created. + +However, for more complex URLs that defined `variables` in them, we generate the types (and any `enum` values or `default` values), and instead use a function to create the URL. + +For a complete example see [`examples/generate/serverurls`](examples/generate/serverurls). + +### Duplicate types generated for clients's response object types + +When generating the types for interacting with the generated client, `oapi-codegen` will use the `operationId` and add on a `Request` or `Response` suffix. + +However, this can clash if you have named your component schemas in a similar way. + +For instance: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: "Show that generated client boilerplate can clash if schemas are well named i.e. `*Request` and `*Response`" +paths: + /client: + put: + operationId: updateClient + requestBodies: + application/json: + schema: + $ref: '#/components/schemas/UpdateClientRequest' + responses: + 400: + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateClientResponse' +components: + schemas: + UpdateClientRequest: + type: object + properties: + code: + type: string + required: + - code + UpdateClientResponse: + type: object + required: + - name + properties: + name: + type: string +``` + +If you were to generate with this configuration: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json +package: client +output: client.gen.go +generate: + models: true + client: true +``` + +This would then result in `go build` failures: + +``` +# github.com/oapi-codegen/oapi-codegen/v2/examples/clienttypenameclash +./client.gen.go:184:6: UpdateClientResponse redeclared in this block + ./client.gen.go:17:6: other declaration of UpdateClientResponse +./client.gen.go:192:7: r.HTTPResponse undefined (type UpdateClientResponse has no field or method HTTPResponse) +./client.gen.go:193:12: r.HTTPResponse undefined (type UpdateClientResponse has no field or method HTTPResponse) +./client.gen.go:200:7: r.HTTPResponse undefined (type UpdateClientResponse has no field or method HTTPResponse) +./client.gen.go:201:12: r.HTTPResponse undefined (type UpdateClientResponse has no field or method HTTPResponse) +./client.gen.go:224:3: unknown field Body in struct literal of type UpdateClientResponse +./client.gen.go:225:3: unknown field HTTPResponse in struct literal of type UpdateClientResponse +./client.gen.go:234:12: response.JSON400 undefined (type *UpdateClientResponse has no field or method JSON400) +``` + +To fix this, use the `response-type-suffix` Output Option: + +```diff + # yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json + package: client + output: client.gen.go + generate: + models: true + client: true ++output-options: ++ response-type-suffix: Resp +``` + +This will then rename the generated types from the default to use the new suffix: + +```diff +-type UpdateClientResponse struct { ++type UpdateClientResp struct { + Body []byte + HTTPResponse *http.Response + JSON400 *UpdateClientResponse + } +``` + +There is no currently planned work to change this behaviour. + +## Generating API models + +If you're looking to only generate the models for interacting with a remote service, for instance if you need to hand-roll the API client for whatever reason, you can do this as-is. + +> [!TIP] +> Try to define as much as possible within the `#/components/schemas` object, as `oapi-codegen` will generate all the types here. +> +> Although we can generate some types based on inline definitions in i.e. a path's response type, it isn't always possible to do this, or if it is generated, can be a little awkward to work with as it may be defined as an anonymous struct. + +For instance, given an `api.yaml`: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: Generate models +paths: + /client: + get: + operationId: getClient + responses: + 200: + content: + application/json: + schema: + # NOTE that Client is generated here, because it's within #/components/schemas + $ref: "#/components/schemas/Client" + put: + operationId: updateClient + responses: + 400: + content: + application/json: + # NOTE that this anonymous object is /not/ generated because it's an anonymous, but would be generated if using `generate: client` + # See https://github.com/oapi-codegen/oapi-codegen/issues/1512 + schema: + type: object + properties: + code: + type: string + required: + - code +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + type: string + # NOTE that this is not generated by default because it's not referenced. If you want it, you need to use the following YAML configuration: + # + # output-options: + # skip-prune: true + Unreferenced: + type: object + required: + - id + properties: + id: + type: integer +``` + +And a `cfg.yaml`: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json +package: onlymodels +output: only-models.gen.go +generate: + models: true +``` + +And a `generate.go`: + +```go +package onlymodels + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml +``` + +This would then generate: + +```go +package onlymodels + +// Client defines model for Client. +type Client struct { + Name string `json:"name"` +} +``` + +If you wish to also generate the `Unreferenced` type, you would need the following `cfg.yaml`: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json +package: onlymodels +output: only-models.gen.go +generate: + models: true +output-options: + # NOTE that this is only required for the `Unreferenced` type + skip-prune: true +``` + +For a complete example see [`examples/only-models`](examples/only-models). + +## Splitting large OpenAPI specs across multiple packages (aka "Import Mapping" or "external references") + + +When you've got a large OpenAPI specification, you may find it useful to split the contents of the spec across multiple files, using external references, such as: + +```yaml + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/User' +``` + +This is supported by `oapi-codegen`, through the ability to perform "Import Mapping". + +For instance, let's say that we have a large API, which has a user-facing API and an admin API, both of which use a common set of API models. + +In this case, we may have an Admin API that looks like: + +```yaml +# admin/api.yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: Admin API + description: The admin-only portion of the API, which has its own separate OpenAPI spec +tags: + - name: admin + description: Admin API endpoints + - name: user + description: API endpoint that pertains to user data +paths: + /admin/user/{id}: + get: + tags: + - admin + - user + summary: Get a user's details + operationId: getUserById + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '../common/api.yaml#/components/schemas/User' +``` + +This references the common spec: + +```yaml +# common/api.yaml +components: + schemas: + User: + type: object + additionalProperties: false + properties: + name: + type: string + required: + - name +``` + +So how do we get `oapi-codegen` to generate our code? + +### Using a single package with multiple OpenAPI specs + + + +> [!TIP] +> Since `oapi-codegen` v2.4.0, it is now possible to split large OpenAPI specifications into the same Go package, using the "self" mapping (denoted by a `-`) when using Import Mapping. +> +> This is an improvement on the previous model, which would require splitting files across multiple packages. + +> [!NOTE] +> You still need to have multiple `go generate`s, and any other configuration files. + +To get `oapi-codegen`'s single-package support working, we need multiple calls to `oapi-codegen`, one call per OpenAPI spec file: + +```sh +$ go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg-api.yaml ../admin/api.yaml +$ go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg-user.yaml ../common/api.yaml +``` + +This therefore means that we need multiple configuration files, such as `cfg-api.yaml`: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json +package: samepackage +output: server.gen.go +generate: + models: true + chi-server: true + strict-server: true +output-options: + # to make sure that all types are generated + skip-prune: true +import-mapping: + user.yaml: "-" +``` + +And then our `cfg-user.yaml`: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json +package: samepackage +output: user.gen.go +generate: + models: true +output-options: + # to make sure that all types are generated + skip-prune: true +``` + +From here, `oapi-codegen` will generate multiple Go files, all within the same package, which can be used to break down your large OpenAPI specifications, and generate only the subsets of code needed for each part of the spec. + +Check out [the import-mapping/samepackage example](examples/import-mapping/samepackage) for the full code. + +### Using multiple packages, with one OpenAPI spec per package + +To get `oapi-codegen`'s multi-package support working, we need to set up our directory structure: + +``` +├── admin +│   ├── cfg.yaml +│   └── generate.go +└── common + ├── cfg.yaml +    └── generate.go +``` + +We could start with our configuration file for our admin API spec: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json +# admin/cfg.yaml +package: admin +output: server.gen.go +generate: + models: true + chi-server: true +output-options: + # to make sure that all types are generated + skip-prune: true +# NOTE that this won't work, as it's missing `import-mapping` +``` + +If we were to run `oapi-codegen`, this will fail with the following error + +``` +error generating code: error creating operation definitions: error generating response definitions: error generating request body definition: error turning reference (../common/api.yaml#/components/schemas/User) into a Go type: unrecognized external reference '../common/api.yaml'; please provide the known import for this reference using option --import-mapping +``` + +This is because `oapi-codegen` requires the `import-mapping`: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json +package: admin +output: server.gen.go +generate: + models: true + chi-server: true +output-options: + # to make sure that all types are generated + skip-prune: true +import-mapping: + # for a given file/URL that is $ref'd, point `oapi-codegen` to the Go package that this spec is generated into, to perform Go package imports + ../common/api.yaml: github.com/oapi-codegen/oapi-codegen/v2/examples/import-mapping/common +``` + +This will then generate the following code: + +```go +package admin + +import ( + // ... + externalRef0 "github.com/oapi-codegen/oapi-codegen/v2/examples/import-mapping/common" +) + +// User defines model for User. +type User = externalRef0.User +``` + +If you don't want to do this, an alternate option is to [use a single package, with multiple OpenAPI spec files for that given package](#import-mapping-self) or to [bundle your multiple OpenAPI files](https://www.jvt.me/posts/2022/02/10/bundle-openapi/) into a single spec. + +Check out [the import-mapping/multiplepackages example](examples/import-mapping/multiplepackages/) for the full code. + +## Modifying the input OpenAPI Specification (with OpenAPI Overlay) + +Prior to `oapi-codegen` v2.4.0, users wishing to override specific configuration, for instance taking advantage of extensions such as `x-go-type` would need to modify the OpenAPI specification they are using. + +In a lot of cases, this OpenAPI specification would be produced by a different team to the consumers (or even a different company) and so asking them to make changes like this were unreasonable. + +This would lead to the API consumers needing to vendor the specification from the producer (which is [our recommendation anyway](#https-paths)) and then make any number of local changes to the specification to make it generate code that looks reasonable. + +However, in the case that a consumer would update their specification, they would likely end up with a number of merge conflicts. + +Now, as of `oapi-codegen` v2.4.0, it is now possible to make changes to the input OpenAPI specification _without needing to modify it directly_. + +This takes advantage of the [OpenAPI Overlay specification](https://github.com/OAI/Overlay-Specification), which is a stable specification. + +> [!CAUTION] +> Beware! Here (may) be dragons. +> +> The Overlay specification requires the use of JSON Path, which some users may find difficult to write and/or maintain. +> +> We still heavily recommend using Overlay functionality, but would like users to be aware of this. +> +> There is a [proposed modification to the specification](https://github.com/OAI/Overlay-Specification/pull/32) which would relax the need for JSON Path as the targeting mechanism. + +For instance, let's say that we have the following OpenAPI specification, which provides insight into an internal endpoint that we should not be generating any code for (denoted by `x-internal`): + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: "Example to indicate how to use the OpenAPI Overlay specification (https://github.com/OAI/Overlay-Specification)" +paths: + /ping: + get: + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pong' + delete: + x-internal: true + responses: + '202': + content: {} +``` + +If we were to run `oapi-codegen` with out-of-the-box functionality, this would then lead to the DELETE endpoint being generated, which we don't want. + +Instead, we can define the following `overlay.yaml`: + + +```yaml +overlay: 1.0.0 +info: + title: Overlay + version: 0.0.0 +actions: +- target: "$" + description: Perform a structural overlay, which can be more readable, as it's clear what the shape of the document is + update: + info: + x-overlay-applied: structured-overlay + paths: + /ping: + get: + responses: + '200': + description: Perform a ping request +- target: $.paths.*[?(@.x-internal)] + description: Remove internal endpoints (noted by x-internal) + remove: true +- target: $.paths.*.*[?(@.x-internal)] + description: Remove internal endpoints (noted by x-internal) + remove: true +``` + +And our configuration file for `oapi-codegen`: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json +package: api +output: ping.gen.go +generate: + models: true + gorilla-server: true + embedded-spec: true +output-options: + overlay: + path: overlay.yaml +``` + +This then completely removes the DELETE endpoint _before_ we even start to parse the specification in `oapi-codegen`, so it's as if your specification was provided without that endpoint. + +Additionally, we can override other pieces of metadata, such as the description for operations. + +Check out [the overlay example](examples/overlay/) for the full code, and some more complex examples. + +## Generating Nullable types + +It's possible that you want to be able to determine whether a field isn't sent, is sent as `null` or has a value. + +For instance, if you had the following OpenAPI property: + +```yaml +S: + type: object + properties: + Field: + type: string + nullable: true + required: [] +``` + +The default behaviour in `oapi-codegen` is to generate: + +```go +type S struct { + Field *string `json:"field,omitempty"` +} +``` + +However, you lose the ability to understand the three cases, as there's no way to distinguish two of the types from each other: + +- is this field not sent? (Can be checked with `S.Field == nil`) +- is this field `null`? (Can be checked with `S.Field == nil`) +- does this field have a value? (`S.Field != nil && *S.Field == "123"`) + +As of `oapi-codegen` [v2.1.0](https://github.com/oapi-codegen/oapi-codegen/releases/tag/v2.1.0) it is now possible to represent this with the `nullable.Nullable` type from [our new library, oapi-codegen/nullable](https://github.com/oapi-codegen/nullable). + +If you configure your generator's Output Options to opt-in to this behaviour, as so: + +```yaml +output-options: + nullable-type: true +``` + +You will now receive the following output: + +```go +type S struct { + // note that there's no pointer here, just `omitempty` + Field nullable.Nullable[string] `json:"field,omitempty"` +} +``` + +## OpenAPI extensions + +As well as the core OpenAPI support, we also support the following OpenAPI extensions, as denoted by the [OpenAPI Specification Extensions](https://spec.openapis.org/oas/v3.0.3#specification-extensions). + +The following extensions are supported: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Extension + +Description +
+ +`x-go-type`
+`x-go-type-import` + +
+Override the generated type definition (and optionally, add an import from another package) +
+ +`x-go-type-skip-optional-pointer` + + +Do not add a pointer type for optional fields in structs +
+ +`x-go-name` + + +Override the generated name of a field or a type +
+ +`x-go-type-name` + + +Override the generated name of a type +
+ +`x-omitempty` + + +Force the presence of the JSON tag `omitempty` on a field +
+ +`x-omitzero` + + +Force the presence of the JSON tag `omitzero` on a field +
+ +`x-go-json-ignore` + + +When (un)marshaling JSON, ignore field(s) +
+ +`x-oapi-codegen-extra-tags` + + +Generate arbitrary struct tags to fields +
+ +`x-enum-varnames` / `x-enumNames` + + +Override generated variable names for enum constants +
+ +`x-deprecated-reason` + + +Add a GoDoc deprecation warning to a type +
+ +`x-order` + + +Explicitly order struct fields +
+ +`x-oapi-codegen-only-honour-go-name` + + +Only honour the `x-go-name` when generating field names +
+ + +### `x-go-type` / `x-go-type-import` - override the generated type definition (and optionally, add an import from another package) + +Using the `x-go-type` (and optionally, `x-go-type-import` when you need to import another package) allows overriding the type that `oapi-codegen` determined the generated type should be. + +We can see this at play with the following schemas: + +```yaml +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + type: string + id: + type: number + ClientWithExtension: + type: object + required: + - name + properties: + name: + type: string + # this is a bit of a contrived example, as you could instead use + # `format: uuid` but it explains how you'd do this when there may be + # a clash, for instance if you already had a `uuid` package that was + # being imported, or ... + x-go-type: googleuuid.UUID + x-go-type-import: + path: github.com/google/uuid + name: googleuuid + id: + type: number + # ... this is also a bit of a contrived example, as you could use + # `type: integer` but in the case that you know better than what + # oapi-codegen is generating, like so: + x-go-type: int64 +``` + +From here, we now get two different models: + +```go +// Client defines model for Client. +type Client struct { + Id *float32 `json:"id,omitempty"` + Name string `json:"name"` +} + +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension struct { + Id *int64 `json:"id,omitempty"` + Name googleuuid.UUID `json:"name"` +} +``` + +You can see this in more detail in [the example code](examples/extensions/xgotype/). + +### `x-go-type-skip-optional-pointer` - do not add a pointer type for optional fields in structs + + + +> [!TIP] +> If you prefer this behaviour, and prefer to not have to annotate your whole OpenAPI spec for this behaviour, you can use `output-options.prefer-skip-optional-pointer=true` to default this behaviour for all fields. +> +> It is then possible to override this on a per-type/per-field basis where necessary. + +By default, `oapi-codegen` will generate a pointer for optional fields. + +Using the `x-go-type-skip-optional-pointer` extension allows omitting that pointer. + +We can see this at play with the following schemas: + +```yaml +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + type: string + id: + type: number + ClientWithExtension: + type: object + required: + - name + properties: + name: + type: string + id: + type: number + x-go-type-skip-optional-pointer: true +``` + +From here, we now get two different models: + +```go +// Client defines model for Client. +type Client struct { + Id *float32 `json:"id,omitempty"` + Name string `json:"name"` +} + +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension struct { + Id float32 `json:"id,omitempty"` + Name string `json:"name"` +} +``` + +You can see this in more detail in [the example code](examples/extensions/xgotypeskipoptionalpointer/). + +### `x-go-name` - override the generated name of a field or a type + +By default, `oapi-codegen` will attempt to generate the name of fields and types in as best a way it can. + +However, sometimes, the name doesn't quite fit what your codebase standards are, or the intent of the field, so you can override it with `x-go-name`. + +We can see this at play with the following schemas: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-go-name +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + type: string + id: + type: number + ClientWithExtension: + type: object + # can be used on a type + x-go-name: ClientRenamedByExtension + required: + - name + properties: + name: + type: string + id: + type: number + # or on a field + x-go-name: AccountIdentifier +``` + +From here, we now get two different models: + +```go +// Client defines model for Client. +type Client struct { + Id *float32 `json:"id,omitempty"` + Name string `json:"name"` +} + +// ClientRenamedByExtension defines model for ClientWithExtension. +type ClientRenamedByExtension struct { + AccountIdentifier *float32 `json:"id,omitempty"` + Name string `json:"name"` +} +``` + +You can see this in more detail in [the example code](examples/extensions/xgoname/). + +### `x-go-type-name` - Override the generated name of a type + +> [!NOTE] +> Notice that this is subtly different to the `x-go-name`, which also applies to _fields_ within `struct`s. + +By default, `oapi-codegen` will attempt to generate the name of types in as best a way it can. + +However, sometimes, the name doesn't quite fit what your codebase standards are, or the intent of the field, so you can override it with `x-go-name`. + +We can see this at play with the following schemas: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-go-type-name +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + type: string + id: + type: number + ClientWithExtension: + type: object + x-go-type-name: ClientRenamedByExtension + required: + - name + properties: + name: + type: string + id: + type: number + # NOTE attempting a `x-go-type-name` here is a no-op, as we're not producing a _type_ only a _field_ + x-go-type-name: ThisWillNotBeUsed +``` + +From here, we now get two different models and a type alias: + +```go +// Client defines model for Client. +type Client struct { + Id *float32 `json:"id,omitempty"` + Name string `json:"name"` +} + +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension = ClientRenamedByExtension + +// ClientRenamedByExtension defines model for . +type ClientRenamedByExtension struct { + Id *float32 `json:"id,omitempty"` + Name string `json:"name"` +} +``` + +You can see this in more detail in [the example code](examples/extensions/xgotypename/). + +### `x-omitempty` - force the presence of the JSON tag `omitempty` on a field + +In a case that you may want to add the JSON struct tag `omitempty` to types that don't have one generated by default - for instance a required field - you can use the `x-omitempty` extension. + +We can see this at play with the following schemas: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-omitempty +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + type: string + id: + type: number + ClientWithExtension: + type: object + required: + - name + properties: + name: + type: string + # for some reason, you may want this behaviour, even though it's a required field + x-omitempty: true + id: + type: number +``` + +From here, we now get two different models: + +```go +// Client defines model for Client. +type Client struct { + Id *float32 `json:"id,omitempty"` + Name string `json:"name"` +} + +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension struct { + Id *float32 `json:"id,omitempty"` + Name string `json:"name,omitempty"` +} +``` + +You can see this in more detail in [the example code](examples/extensions/xomitempty/). + +### `x-omitzero` - force the presence of the JSON tag `omitzero` on a field + +> [!NOTE] +> `omitzero` was added in Go 1.24. If you're not using Go 1.24 in your project, this won't work. + +In a case that you may want to add the JSON struct tag `omitzero` to types, you can use the `x-omitempty` extension. + +We can see this at play with the following schemas: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-omitempty +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + type: string + id: + type: number + ClientWithExtension: + type: object + required: + - name + properties: + name: + type: string + id: + type: number + x-omitzero: true +``` + +From here, we now get two different models: + +```go +// Client defines model for Client. +type Client struct { + Id *float32 `json:"id,omitempty"` + Name string `json:"name"` +} + +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension struct { + Id *float32 `json:"id,omitempty,omitzero"` + Name string `json:"name"` +} +``` + +You can see this in more detail in [the example code](examples/extensions/xomitzero/). + +### `x-go-json-ignore` - when (un)marshaling JSON, ignore field(s) + +By default, `oapi-codegen` will generate `json:"..."` struct tags for all fields in a struct, so JSON (un)marshaling works. + +However, sometimes, you want to omit fields, which can be done with the `x-go-json-ignore` extension. + +We can see this at play with the following schemas: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-go-json-ignore +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + type: string + complexField: + type: object + properties: + name: + type: string + accountName: + type: string + # ... + ClientWithExtension: + type: object + required: + - name + properties: + name: + type: string + complexField: + type: object + properties: + name: + type: string + accountName: + type: string + # ... + x-go-json-ignore: true +``` + +From here, we now get two different models: + +```go +// Client defines model for Client. +type Client struct { + ComplexField *struct { + AccountName *string `json:"accountName,omitempty"` + Name *string `json:"name,omitempty"` + } `json:"complexField,omitempty"` + Name string `json:"name"` +} + +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension struct { + ComplexField *struct { + AccountName *string `json:"accountName,omitempty"` + Name *string `json:"name,omitempty"` + } `json:"-"` + Name string `json:"name"` +} +``` + +Notice that the `ComplexField` is still generated in full, but the type will then be ignored with JSON marshalling. + +You can see this in more detail in [the example code](examples/extensions/xgojsonignore/). + +### `x-oapi-codegen-extra-tags` - generate arbitrary struct tags to fields + +If you're making use of a field's struct tags to i.e. apply validation, decide whether something should be logged, etc, you can use `x-oapi-codegen-extra-tags` to set additional tags for your generated types. + +We can see this at play with the following schemas: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-oapi-codegen-extra-tags +components: + schemas: + Client: + type: object + required: + - name + - id + properties: + name: + type: string + id: + type: number + ClientWithExtension: + type: object + required: + - name + - id + properties: + name: + type: string + id: + type: number + x-oapi-codegen-extra-tags: + validate: "required,min=1,max=256" + safe-to-log: "true" + gorm: primarykey +``` + +From here, we now get two different models: + +```go +// Client defines model for Client. +type Client struct { + Id float32 `json:"id"` + Name string `json:"name"` +} + +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension struct { + Id float32 `gorm:"primarykey" json:"id" safe-to-log:"true" validate:"required,min=1,max=256"` + Name string `json:"name"` +} +``` + +You can see this in more detail in [the example code](examples/extensions/xoapicodegenextratags/). + +### `x-enum-varnames` / `x-enumNames` - override generated variable names for enum constants + +When consuming an enum value from an external system, the name may not produce a nice variable name. Using the `x-enum-varnames` extension allows overriding the name of the generated variable names. + +We can see this at play with the following schemas: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-enumNames and x-enum-varnames +components: + schemas: + ClientType: + type: string + enum: + - ACT + - EXP + ClientTypeWithNamesExtension: + type: string + enum: + - ACT + - EXP + x-enumNames: + - Active + - Expired + ClientTypeWithVarNamesExtension: + type: string + enum: + - ACT + - EXP + x-enum-varnames: + - Active + - Expired +``` + +From here, we now get two different forms of the same enum definition. + +```go +// Defines values for ClientType. +const ( + ACT ClientType = "ACT" + EXP ClientType = "EXP" +) + +// Defines values for ClientTypeWithNamesExtension. +const ( + ClientTypeWithNamesExtensionActive ClientTypeWithNamesExtension = "ACT" + ClientTypeWithNamesExtensionExpired ClientTypeWithNamesExtension = "EXP" +) + +// Defines values for ClientTypeWithVarNamesExtension. +const ( + ClientTypeWithVarNamesExtensionActive ClientTypeWithVarNamesExtension = "ACT" + ClientTypeWithVarNamesExtensionExpired ClientTypeWithVarNamesExtension = "EXP" +) + +// ClientType defines model for ClientType. +type ClientType string + +// ClientTypeWithNamesExtension defines model for ClientTypeWithNamesExtension. +type ClientTypeWithNamesExtension string + +// ClientTypeWithVarNamesExtension defines model for ClientTypeWithVarNamesExtension. +type ClientTypeWithVarNamesExtension string +``` + +You can see this in more detail in [the example code](examples/extensions/xenumnames/). + +### `x-deprecated-reason` - add a GoDoc deprecation warning to a type + +When an OpenAPI type is deprecated, a deprecation warning can be added in the GoDoc using `x-deprecated-reason`. + +We can see this at play with the following schemas: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-deprecated-reason +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + type: string + id: + type: number + ClientWithExtension: + type: object + required: + - name + properties: + name: + type: string + deprecated: true + x-deprecated-reason: Don't use because reasons + id: + type: number + # NOTE that this doesn't generate, as no `deprecated: true` is set + x-deprecated-reason: NOTE you shouldn't see this, as you've not deprecated this field +``` + +From here, we now get two different forms of the same enum definition. + +```go +// Client defines model for Client. +type Client struct { + Id *float32 `json:"id,omitempty"` + Name string `json:"name"` +} + +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension struct { + Id *float32 `json:"id,omitempty"` + // Deprecated: Don't use because reasons + Name string `json:"name"` +} +``` + +Notice that because we've not set `deprecated: true` to the `name` field, it doesn't generate a deprecation warning. + +You can see this in more detail in [the example code](examples/extensions/xdeprecatedreason/). + +### `x-order` - explicitly order struct fields + +Whether you like certain fields being ordered before others, or you want to perform more efficient packing of your structs, the `x-order` extension is here for you. + +Note that `x-order` is 1-indexed - `x-order: 0` is not a valid value. + +We can see this at play with the following schemas: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-order +components: + schemas: + Client: + type: object + required: + - name + properties: + a_name: + type: string + id: + type: number + ClientWithExtension: + type: object + required: + - name + properties: + a_name: + type: string + x-order: 2 + id: + type: number + x-order: 1 +``` + +From here, we now get two different forms of the same type definition. + +```go +// Client defines model for Client. +type Client struct { + AName *string `json:"a_name,omitempty"` + Id *float32 `json:"id,omitempty"` +} + +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension struct { + Id *float32 `json:"id,omitempty"` + AName *string `json:"a_name,omitempty"` +} +``` + +You can see this in more detail in [the example code](examples/extensions/xorder/). + +### `x-oapi-codegen-only-honour-go-name` - only honour the `x-go-name` when generating field names + + +> [!WARNING] +> Using this option may lead to cases where `oapi-codegen`'s rewriting of field names to prevent clashes with other types, or to prevent including characters that may not be valid Go field names. + +In some cases, you may not want use the inbuilt options for converting an OpenAPI field name to a Go field name, such as the `name-normalizer: "ToCamelCaseWithInitialisms"`, and instead trust the name that you've defined for the type better. + +In this case, you can use `x-oapi-codegen-only-honour-go-name` to enforce this, alongside specifying the `allow-unexported-struct-field-names` compatibility option. + +This allows you to take a spec such as: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-oapi-codegen-only-honour-go-name +components: + schemas: + TypeWithUnexportedField: + description: A struct will be output where one of the fields is not exported + properties: + name: + type: string + id: + type: string + # NOTE that there is an explicit usage of a lowercase character + x-go-name: accountIdentifier + x-oapi-codegen-extra-tags: + json: "-" + x-oapi-codegen-only-honour-go-name: true +``` + +And we'll generate: + +```go +// TypeWithUnexportedField A struct will be output where one of the fields is not exported +type TypeWithUnexportedField struct { + accountIdentifier *string `json:"-"` + Name *string `json:"name,omitempty"` +} +``` + +You can see this in more detail in [the example code](examples/extensions/xoapicodegenonlyhonourgoname). + +## Request/response validation middleware + +The generated code that `oapi-codegen` produces has some validation for some incoming data, such as checking for required headers, and when using the [strict server](#strict-server) you get some more validation around the correct usage of the response types. + +However, this leaves a lot of validation that needs to be done, which can be tedious to hand-write this logic, especially for large or complex OpenAPI specifications. + +To simplify this, we use a middleware, which provides the request validation. The middleware you want to use depends on the server you're using: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Server + +Middleware library +
+ +[Chi](https://github.com/go-chi/chi) + + + +[nethttp-middleware](https://github.com/oapi-codegen/nethttp-middleware) + +
+ +[Echo](https://github.com/labstack/echo) + + + +[echo-middleware](https://github.com/oapi-codegen/echo-middleware) + +
+ +[Fiber](https://github.com/gofiber/fiber) + + + +[fiber-middleware](https://github.com/oapi-codegen/fiber-middleware) + +
+ +[Gin](https://github.com/gin-gonic/gin) + + + +[gin-middleware](https://github.com/oapi-codegen/gin-middleware) + +
+ +[gorilla/mux](https://github.com/gorilla/mux) + + + +[nethttp-middleware](https://github.com/oapi-codegen/nethttp-middleware) + +
+ +[Iris](https://github.com/kataras/iris) + + + +[iris-middleware](https://github.com/oapi-codegen/iris-middleware) + +
+ +[1.22+ `net/http`](https://pkg.go.dev/net/http) + + + +[nethttp-middleware](https://github.com/oapi-codegen/nethttp-middleware) + +
+ +Any other server (which conforms to `net/http`) + + + +[nethttp-middleware](https://github.com/oapi-codegen/nethttp-middleware) + +
+ +> [!NOTE] +> It is [not currently possible](https://github.com/oapi-codegen/oapi-codegen/issues/1038) to validate the HTTP response with a middleware. + +> [!NOTE] +> We're also [exploring](https://github.com/oapi-codegen/exp/issues/1) the use of [libopenapi-validator](https://github.com/pb33f/libopenapi-validator/) for request/response validation middleware + +## Implementing security + +If you're using a specification with [Security Schemes](https://spec.openapis.org/oas/v3.0.3#security-scheme-object) and [Security Requirements](https://spec.openapis.org/oas/v3.0.3#security-requirement-object), you'll want to authenticate and authorize requests. + +### On the server + +> [!NOTE] +> Out-of-the-box, the server-side code generated by `oapi-codegen` does not provide security validation. +> +> To perform authentication, you will need to use the [validation middleware](#requestresponse-validation-middleware). +> +> In the future, we plan to [implement server-side validation in the generated code](https://github.com/oapi-codegen/oapi-codegen/issues/1524) + +To see how this can work, check out the [authenticated API example](examples/authenticated-api/echo). + +### On the client + +With a generated client, you'll want to use the client's generated `WithRequestEditorFn` function to pass in a given request editor `RequestEditorFn`. + +For instance: -The `/components/schemas` section in OpenAPI defines reusable objects, so Go -types are generated for these. The Pet Store example defines `Error`, `Pet`, -`Pets` and `NewPet`, so we do the same in Go: ```go -// Error defines model for Error. -type Error struct { - // Error code - Code int32 `json:"code"` +import ( + "context" + "fmt" + "log" + + "github.com/oapi-codegen/oapi-codegen/v2/pkg/securityprovider" +) + +func main() { + basicAuth, err := securityprovider.NewSecurityProviderBasicAuth("my_user", "my_pass") + if err != nil { + log.Fatal(err) + } + + client, err := NewClient("https://....", WithRequestEditorFn(basicAuth.Intercept)) + if err != nil { + log.Fatal(err) + } + + resp, err := client.GetClient(context.TODO()) + if err != nil { + log.Fatal(err) + } + fmt.Printf("resp.StatusCode: %v\n", resp.StatusCode) +} +``` + +Notice that we're using a pre-built provider from the [`pkg/securityprovider` package](https://pkg.go.dev/github.com/oapi-codegen/oapi-codegen/v2/pkg/securityprovider), which has some inbuilt support for other types of authentication, too. + +## Custom code generation + +It is possible to extend the inbuilt code generation from `oapi-codegen` using Go's `text/template`s. + +You can specify, through your configuration file, the `output-options.user-templates` setting to override the inbuilt templates and use a user-defined template. + +> [!NOTE] +> Filenames given to the `user-templates` configuration must **exactly** match the filename that `oapi-codegen` is looking for + +### Local paths + +Within your configuration file, you can specify relative or absolute paths to a file to reference for the template, such as: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json +# ... +output-options: + user-templates: + client-with-responses.tmpl: ./custom-template.tmpl + additional-properties.tmpl: /tmp/foo.bar + typedef.tmpl: no-prefix.tmpl +``` + +> [!WARNING] +> We do not interpolate `~` or `$HOME` (or other environment variables) in paths given + +### HTTPS paths + +It is also possible to use HTTPS URLs. + +> [!WARNING] +> Although possible, this does lead to `oapi-codegen` executions not necessarily being reproducible. It's recommended to vendor (copy) the OpenAPI spec into your codebase and reference it locally +> +> See [this blog post](https://www.jvt.me/posts/2024/04/27/github-actions-update-file/) for an example of how to use GitHub Actions to manage the updates of files across repos +> +> See [this blog post](https://www.jvt.me/posts/2026/02/27/renovate-update-file) for an example of how to use Renovate to manage the updates of files across repos +> +> This will be disabled by default (but possible to turn back on via configuration) [in the future](https://github.com/oapi-codegen/oapi-codegen/issues/1564) + +To use it, you can use the following configuration: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json +# ... +output-options: + user-templates: + # The following are referencing a version of the default client-with-responses.tmpl file, but loaded in through GitHub's raw.githubusercontent.com. The general form to use raw.githubusercontent.com is as follows https://raw.githubusercontent.com////path/to/template/template.tmpl + + # Alternatively using raw.githubusercontent.com with a hash + client-with-responses.tmpl: https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/ad5eada4f3ccc28a88477cef62ea21c17fc8aa01/pkg/codegen/templates/client-with-responses.tmpl + # Alternatively using raw.githubusercontent.com with a tag + client-with-responses.tmpl: https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/v2.1.0/pkg/codegen/templates/client-with-responses.tmpl + # Alternatively using raw.githubusercontent.com with a branch + client-with-responses.tmpl: https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/master/pkg/codegen/templates/client-with-responses.tmpl +``` + +> [!WARNING] +> If using URLs that pull locations from a Git repo, such as `raw.githubusercontent.com`, it is strongly encouraged to use a tag or a raw commit hash instead of a branch like `main`. Tracking a branch can lead to unexpected API drift, and loss of the ability to reproduce a build. + +### Inline template + +It's also possible to set the templates inline in the configuration file: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json +# ... +output-options: + user-templates: + # NOTE the use of the `|` (pipe symbol) here to denote that this is a + # multi-line statement that should preserve newlines. More reading: + # https://stackoverflow.com/a/18708156/2257038 and + # https://stackoverflow.com/a/15365296/2257038 + client-with-responses.tmpl: | + // ClientWithResponses builds on ClientInterface to offer response payloads + type ClientWithResponses struct { + ClientInterface + } + ... +``` + +### Using the Go package + +Alternatively, you are able to use the underlying code generation as a package, which [will be documented in the future](https://github.com/oapi-codegen/oapi-codegen/issues/1487). + +## Additional Properties (`additionalProperties`) + +[OpenAPI Schemas](https://spec.openapis.org/oas/v3.0.3.html#schema-object) implicitly accept `additionalProperties`, meaning that any fields provided, but not explicitly defined via properties on the schema are accepted as input, and propagated. When unspecified, OpenAPI defines that the `additionalProperties` field is assumed to be `true`. + +For simplicity, and to remove a fair bit of duplication and boilerplate, `oapi-codegen` decides to ignore the implicit `additionalProperties: true`, and instead requires you to specify the `additionalProperties` key to generate the boilerplate. + +> [!NOTE] +> In the future [this will be possible](https://github.com/oapi-codegen/oapi-codegen/issues/1514) to disable this functionality, and honour the implicit `additionalProperties: true` + +Below you can see some examples of how `additionalProperties` affects the generated code. + +### Implicit `additionalProperties: true` / no `additionalProperties` set + +```yaml +components: + schemas: + Thing: + type: object + required: + - id + properties: + id: + type: integer + # implicit additionalProperties: true +``` + +Will generate: + +```go +// Thing defines model for Thing. +type Thing struct { + Id int `json:"id"` +} + +// with no generated boilerplate nor the `AdditionalProperties` field +``` + +### Explicit `additionalProperties: true` + +```yaml +components: + schemas: + Thing: + type: object + required: + - id + properties: + id: + type: integer + # explicit true + additionalProperties: true +``` + +Will generate: - // Error message - Message string `json:"message"` +```go +// Thing defines model for Thing. +type Thing struct { + Id int `json:"id"` + AdditionalProperties map[string]interface{} `json:"-"` } -// NewPet defines model for NewPet. -type NewPet struct { - // Name of the pet - Name string `json:"name"` +// with generated boilerplate below +``` - // Type of the pet - Tag *string `json:"tag,omitempty"` -} +
-// Pet defines model for Pet. -type Pet struct { - // Unique id of the pet - Id int64 `json:"id"` +Boilerplate - // Name of the pet - Name string `json:"name"` +```go - // Type of the pet - Tag *string `json:"tag,omitempty"` +// Getter for additional properties for Thing. Returns the specified +// element and whether it was found +func (a Thing) Get(fieldName string) (value interface{}, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return } -// Type definition for component schema "Pets" -type Pets []Pet -``` +// Setter for additional properties for Thing +func (a *Thing) Set(fieldName string, value interface{}) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]interface{}) + } + a.AdditionalProperties[fieldName] = value +} -It's best to define objects under `/components` field in the schema, since -those will be turned into named Go types. If you use inline types in your -handler definitions, we will generate inline, anonymous Go types, but those -are more tedious to deal with since you will have to redeclare them at every -point of use. +// Override default JSON handling for Thing to handle AdditionalProperties +func (a *Thing) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } -For each element in the `paths` map in OpenAPI, we will generate a Go handler -function in an interface object. Here is the generated Go interface for our -Echo server. + if raw, found := object["id"]; found { + err = json.Unmarshal(raw, &a.Id) + if err != nil { + return fmt.Errorf("error reading 'id': %w", err) + } + delete(object, "id") + } -```go -type ServerInterface interface { - // (GET /pets) - FindPets(ctx echo.Context, params FindPetsParams) error - // (POST /pets) - AddPet(ctx echo.Context) error - // (DELETE /pets/{id}) - DeletePet(ctx echo.Context, id int64) error - // (GET /pets/{id}) - FindPetById(ctx echo.Context, id int64) error + if len(object) != 0 { + a.AdditionalProperties = make(map[string]interface{}) + for fieldName, fieldBuf := range object { + var fieldVal interface{} + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil } -``` -These are the functions which you will implement yourself in order to create -a server conforming to the API specification. Normally, all the arguments and -parameters are stored on the `echo.Context` in handlers, so we do the tedious -work of unmarshalling the JSON automatically, simply passing values into -your handlers. +// Override default JSON handling for Thing to handle AdditionalProperties +func (a Thing) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) -Notice that `FindPetById` takes a parameter `id int64`. All path arguments -will be passed as arguments to your function, since they are mandatory. + object["id"], err = json.Marshal(a.Id) + if err != nil { + return nil, fmt.Errorf("error marshaling 'id': %w", err) + } -Remaining arguments can be passed in headers, query arguments or cookies. Those -will be written to a `params` object. Look at the `FindPets` function above, it -takes as input `FindPetsParams`, which is defined as follows: - ```go -// Parameters object for FindPets -type FindPetsParams struct { - Tags *[]string `json:"tags,omitempty"` - Limit *int32 `json:"limit,omitempty"` + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) } ``` -The HTTP query parameter `limit` turns into a Go field named `Limit`. It is -passed by pointer, since it is an optional parameter. If the parameter is -specified, the pointer will be non-`nil`, and you can read its value. +
-If you changed the OpenAPI specification to make the parameter required, the -`FindPetsParams` structure will contain the type by value: -```go -type FindPetsParams struct { - Tags *[]string `json:"tags,omitempty"` - Limit int32 `json:"limit"` -} -``` -### Registering handlers -There are a few ways of registering your http handler based on the type of server generated i.e. `-generate server` or `-generate chi-server` +### `additionalProperties` as `integer`s -
Echo +```yaml +components: + schemas: + Thing: + type: object + required: + - id + properties: + id: + type: integer + # simple type + additionalProperties: + type: integer +``` -Code generated using `-generate server`. +Will generate: -The usage of `Echo` is out of scope of this doc, but once you have an -echo instance, we generate a utility function to help you associate your handlers -with this autogenerated code. For the pet store, it looks like this: ```go -func RegisterHandlers(router codegen.EchoRouter, si ServerInterface) { - wrapper := ServerInterfaceWrapper{ - Handler: si, - } - router.GET("/pets", wrapper.FindPets) - router.POST("/pets", wrapper.AddPet) - router.DELETE("/pets/:id", wrapper.DeletePet) - router.GET("/pets/:id", wrapper.FindPetById) +// Thing defines model for Thing. +type Thing struct { + Id int `json:"id"` + AdditionalProperties map[string]int `json:"-"` } + +// with generated boilerplate below ``` -The wrapper functions referenced above contain generated code which pulls -parameters off the `Echo` request context, and unmarshals them into Go objects. +
+ +Boilerplate -You would register the generated handlers as follows: ```go -func SetupHandler() { - var myApi PetStoreImpl // This implements the pet store interface - e := echo.New() - petstore.RegisterHandlers(e, &myApi) - ... +// Getter for additional properties for Thing. Returns the specified +// element and whether it was found +func (a Thing) Get(fieldName string) (value int, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return } -``` -
+// Setter for additional properties for Thing +func (a *Thing) Set(fieldName string, value int) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]int) + } + a.AdditionalProperties[fieldName] = value +} -
Chi +// Override default JSON handling for Thing to handle AdditionalProperties +func (a *Thing) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } -Code generated using `-generate chi-server`. + if raw, found := object["id"]; found { + err = json.Unmarshal(raw, &a.Id) + if err != nil { + return fmt.Errorf("error reading 'id': %w", err) + } + delete(object, "id") + } -```go -type PetStoreImpl struct {} -func (*PetStoreImpl) GetPets(w http.ResponseWriter, r *http.Request) { - // Implement me + if len(object) != 0 { + a.AdditionalProperties = make(map[string]int) + for fieldName, fieldBuf := range object { + var fieldVal int + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil } -func SetupHandler() { - var myApi PetStoreImpl +// Override default JSON handling for Thing to handle AdditionalProperties +func (a Thing) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + object["id"], err = json.Marshal(a.Id) + if err != nil { + return nil, fmt.Errorf("error marshaling 'id': %w", err) + } - r := chi.NewRouter() - r.Mount("/", Handler(&myApi)) + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) } ``` -
-
Gin +
-Code generated using `-generate gin`. +### `additionalProperties` with an object -The usage of `gin` is out of scope of this doc, but once you have an -gin instance, we generate a utility function to help you associate your handlers -with this autogenerated code. For the pet store, it looks like this: -```go -// RegisterHandlersWithOptions creates http.Handler with additional options -func RegisterHandlersWithOptions(router *gin.Engine, si ServerInterface, options GinServerOptions) *gin.Engine { - wrapper := ServerInterfaceWrapper{ - Handler: si, - HandlerMiddlewares: options.Middlewares, - } +```yaml +components: + schemas: + Thing: + type: object + required: + - id + properties: + id: + type: integer + # object + additionalProperties: + type: object + properties: + foo: + type: string +``` + +Will generate: - router.GET(options.BaseURL+"/pets", wrapper.FindPets) - router.POST(options.BaseURL+"/pets", wrapper.AddPet) - router.DELETE(options.BaseURL+"/pets/:id", wrapper.DeletePet) - router.GET(options.BaseURL+"/pets/:id", wrapper.FindPetByID) - return router +```go +// Thing defines model for Thing. +type Thing struct { + Id int `json:"id"` + AdditionalProperties map[string]struct { + Foo *string `json:"foo,omitempty"` + } `json:"-"` } + +// with generated boilerplate below ``` +
+ +Boilerplate + ```go -import ( - "github.com/gin-gonic/gin" - "github.com/deepmap/oapi-codegen/examples/petstore-expanded/gin/api" - middleware "github.com/deepmap/oapi-codegen/pkg/gin-middleware" -) +// Getter for additional properties for Thing. Returns the specified +// element and whether it was found +func (a Thing) Get(fieldName string) (value struct { + Foo *string `json:"foo,omitempty"` +}, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for Thing +func (a *Thing) Set(fieldName string, value struct { + Foo *string `json:"foo,omitempty"` +}) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]struct { + Foo *string `json:"foo,omitempty"` + }) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for Thing to handle AdditionalProperties +func (a *Thing) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["id"]; found { + err = json.Unmarshal(raw, &a.Id) + if err != nil { + return fmt.Errorf("error reading 'id': %w", err) + } + delete(object, "id") + } -type PetStoreImpl struct {} -func (*PetStoreImpl) GetPets(w http.ResponseWriter, r *http.Request) { - // Implement me + if len(object) != 0 { + a.AdditionalProperties = make(map[string]struct { + Foo *string `json:"foo,omitempty"` + }) + for fieldName, fieldBuf := range object { + var fieldVal struct { + Foo *string `json:"foo,omitempty"` + } + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil } -func SetupHandler() { - var myApi PetStoreImpl +// Override default JSON handling for Thing to handle AdditionalProperties +func (a Thing) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + object["id"], err = json.Marshal(a.Id) + if err != nil { + return nil, fmt.Errorf("error marshaling 'id': %w", err) + } - r := gin.Default() - r.Use(middleware.OapiRequestValidator(swagger)) - r = api.RegisterHandlers(r, petStore) + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) } ``` -
-
net/http +
+ +## Globally skipping the "optional pointer" + +One of the key things `oapi-codegen` does is to use an "optional pointer", following idiomatic Go practices, to indicate that a field/type is optional. + +This can be tuned on a per-field basis, using the [`x-go-type-skip-optional-pointer` extension](#ext-x-go-type-skip-optional-pointer), but it can be a bit repetitive, or can be more complex when using an OpenAPI Overlay. + +As of `oapi-codegen` v2.5.0, this can be tuned in two specific ways, via the following `output-options:`: + +- `prefer-skip-optional-pointer`: a global default that you do _not_ want the "optional pointer" generated. Optional fields will not have an "optional pointer", and will have an `omitempty` JSON tag +- `prefer-skip-optional-pointer-with-omitzero`: when used in conjunction with `prefer-skip-optional-pointer`, any optional fields are generated with an `omitzero` JSON tag. **Requires Go 1.24+** + +In both cases, there is control on a per-field level to set `x-go-type-skip-optional-pointer: false` or `x-omitzero: false` to undo these to field(s). + +For example, when combining both options: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json +package: preferskipoptionalpointerwithomitzero +output: gen.go +generate: + # ... +output-options: + # ... + prefer-skip-optional-pointer: true + prefer-skip-optional-pointer-with-omitzero: true +``` + +When we have the following spec: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: prefer-skip-optional-pointer-with-omitzero +components: + schemas: + ClientWithExtension: + type: object + required: + - name + properties: + name: + description: This field is required, so will never have an optional pointer, nor `omitzero`. + type: string + id: + description: This field is optional, but the `prefer-skip-optional-pointer` Output Option ensures that this should not have an optional pointer. However, it will receive `omitzero`. + type: number + pointer_id: + type: number + description: This field should have an optional pointer, as the field-level definition of `x-go-type-skip-optional-pointer` overrides the `prefer-skip-optional-pointer` Output Option. This will also not receive an `omitzero`. + # NOTE that this overrides the global preference + x-go-type-skip-optional-pointer: false + no_omit: + type: number + description: This field is optional, but the `prefer-skip-optional-pointer` Output Option ensures that this should not have an optional pointer. This will not receive `omitzero`, as the field-level definition of `x-omitzero` overrides the `prefer-skip-optional-pointer-with-omitzero` Output Option. + # NOTE that this overrides the global preference + x-omitzero: false +``` -[Chi](https://github.com/go-chi/chi) is 100% compatible with `net/http` allowing the following with code generated using `-generate chi-server`. +We then generate the following Go code: ```go -type PetStoreImpl struct {} -func (*PetStoreImpl) GetPets(w http.ResponseWriter, r *http.Request) { - // Implement me -} +// ... -func SetupHandler() { - var myApi PetStoreImpl +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension struct { + // Id This field is optional, but the `prefer-skip-optional-pointer` Output Option ensures that this should not have an optional pointer. However, it will receive `omitzero`. + Id float32 `json:"id,omitempty,omitzero"` - http.Handle("/", Handler(&myApi)) + // Name This field is required, so will never have an optional pointer, nor `omitzero`. + Name string `json:"name"` + + // NoOmit This field is optional, but the `prefer-skip-optional-pointer` Output Option ensures that this should not have an optional pointer. This will not receive `omitzero`, as the field-level definition of `x-omitzero` overrides the `prefer-skip-optional-pointer-with-omitzero` Output Option. + NoOmit float32 `json:"no_omit,omitempty"` + + // PointerId This field should have an optional pointer, as the field-level definition of `x-go-type-skip-optional-pointer` overrides the `prefer-skip-optional-pointer` Output Option. This will also not receive an `omitzero`. + PointerId *float32 `json:"pointer_id,omitempty"` } ``` -Alternatively, [Gorilla](https://github.com/gorilla/mux) is also 100% compatible with `net/http` and can be generated with `-generate gorilla`. +You can see this in more detail in [the example code for `prefer-skip-optional-pointer`](examples/output-options/preferskipoptionalpointer/) and [example code for `prefer-skip-optional-pointer-with-omitzero`](examples/output-options/preferskipoptionalpointerwithomitzero/) -
+## Changing the names of generated types -#### Strict server generation +As of `oapi-codegen` v2.2.0, it is now possible to use the `output-options` configuration's `name-normalizer` to define the logic for how to convert an OpenAPI name (i.e. an Operation ID or a Schema name) and construct a Go type name. -oapi-codegen also supports generating RPC inspired strict server, that will parse request bodies and encode responses. -The main points of this code is to automate some parsing, abstract user code from server specific code, -and also to force user code to comply with the schema. -It supports binding of `application/json` and `application/x-www-form-urlencoded` to a struct, for `multipart` requests -it generates a `multipart.Reader`, which can be used to either manually iterating over parts or using `runtime.BindMultipart` -function to bind the form to a struct. All other content types are represented by a `io.Reader` interface. +
-To form a response simply return one of the generated structs with corresponding status code and content type. For example, -to return a status code 200 JSON response for a AddPet use the `AddPet200JSONResponse` struct which will set the correct -Content-Type header, status code and will marshal the response data. You can also return an error, that will -cause an `Internal Server Error` response. +Example, using default configuration -Short example: -```go -type PetStoreImpl struct {} -func (*PetStoreImpl) GetPets(ctx context.Context, request GetPetsRequestObject) (GetPetsResponseObject, error) { - var result []Pet - // Implement me - return GetPets200JSONResponse(result), nil -} +By default, `oapi-codegen` will perform camel-case conversion, so for a spec such as: + +```yaml +openapi: "3.0.0" +info: + version: 1.0.0 + title: Example code for the `name-normalizer` output option +paths: + /api/pets/{petId}: + get: + summary: Get pet given identifier. + operationId: getHttpPet + parameters: + - name: petId + in: path + required: true + schema: + type: string + responses: + '200': + description: valid pet + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' +components: + schemas: + Pet: + type: object + required: + - uuid + - name + properties: + uuid: + type: string + description: The pet uuid. + name: + type: string + description: The name of the pet. + Error: + required: + - code + - message + properties: + code: + type: integer + format: int32 + description: Error code + message: + type: string + description: Error message + OneOf2things: + description: "Notice that the `things` is not capitalised" + oneOf: + - type: object + required: + - id + properties: + id: + type: integer + - type: object + required: + - id + properties: + id: + type: string + format: uuid ``` -For a complete example see `/examples/petstore-expanded/strict`. -Code is generated with a configuration flag `generate: strict-server: true` along with any other server (echo, chi, gin and gorilla are supported). -The generated strict wrapper can then be used as an implementation for `ServerInterface`. Setup example: +This will produce: + ```go -func SetupHandler() { - var myApi PetStoreImpl - myStrictApiHandler := api.NewStrictHandler(myApi, nil) - e := echo.New() - petstore.RegisterHandlers(e, &myStrictApiHandler) +// OneOf2things Notice that the `things` is not capitalised +type OneOf2things struct { + union json.RawMessage +} + +// Pet defines model for Pet. +type Pet struct { + // Name The name of the pet. + Name string `json:"name"` + + // Uuid The pet uuid. + Uuid string `json:"uuid"` +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetHttpPet request + GetHttpPet(ctx context.Context, petId string, reqEditors ...RequestEditorFn) (*http.Response, error) } ``` -Strict server also has its own middlewares. It can access to both request and response structs, -as well as raw request\response data. It can be used for logging the parsed request\response objects, transforming go errors into response structs, -authorization, etc. Note that middlewares are server-specific. +
-#### Additional Properties in type definitions +
-[OpenAPI Schemas](https://swagger.io/specification/#schemaObject) implicitly -accept `additionalProperties`, meaning that any fields provided, but not explicitly -defined via properties on the schema are accepted as input, and propagated. When -unspecified, the `additionalProperties` field is assumed to be `true`. +Example, using ToCamelCaseWithInitialisms -Additional properties are tricky to support in Go with typing, and require -lots of boilerplate code, so in this library, we assume that `additionalProperties` -defaults to `false` and we don't generate this boilerplate. If you would like -an object to accept `additionalProperties`, specify a schema for `additionalProperties`. +By default, `oapi-codegen` will perform camel-case conversion, so for a spec such as: -Say we declared `NewPet` above like so: ```yaml - NewPet: +openapi: "3.0.0" +info: + version: 1.0.0 + title: Example code for the `name-normalizer` output option +paths: + /api/pets/{petId}: + get: + summary: Get pet given identifier. + operationId: getHttpPet + parameters: + - name: petId + in: path + required: true + schema: + type: string + responses: + '200': + description: valid pet + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' +components: + schemas: + Pet: + type: object required: + - uuid - name properties: + uuid: + type: string + description: The pet uuid. name: type: string - tag: + description: The name of the pet. + Error: + required: + - code + - message + properties: + code: + type: integer + format: int32 + description: Error code + message: type: string - additionalProperties: - type: string + description: Error message + OneOf2things: + description: "Notice that the `things` is not capitalised" + oneOf: + - type: object + required: + - id + properties: + id: + type: integer + - type: object + required: + - id + properties: + id: + type: string + format: uuid ``` -The Go code for `NewPet` would now look like this: +This will produce: + ```go -// NewPet defines model for NewPet. -type NewPet struct { - Name string `json:"name"` - Tag *string `json:"tag,omitempty"` - AdditionalProperties map[string]string `json:"-"` +// OneOf2things Notice that the `things` is not capitalised +type OneOf2things struct { + union json.RawMessage +} + +// Pet defines model for Pet. +type Pet struct { + // Name The name of the pet. + Name string `json:"name"` + + // UUID The pet uuid. + UUID string `json:"uuid"` +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetHTTPPet request + GetHTTPPet(ctx context.Context, petID string, reqEditors ...RequestEditorFn) (*http.Response, error) } ``` -The additionalProperties, of type `string` become `map[string]string`, which maps -field names to instances of the `additionalProperties` schema. -```go -// Getter for additional properties for NewPet. Returns the specified -// element and whether it was found -func (a NewPet) Get(fieldName string) (value string, found bool) {...} +
-// Setter for additional properties for NewPet -func (a *NewPet) Set(fieldName string, value string) {...} -// Override default JSON handling for NewPet to handle additionalProperties -func (a *NewPet) UnmarshalJSON(b []byte) error {...} +For more details of what the resulting code looks like, check out [the test cases](internal/test/outputoptions/name-normalizer/). -// Override default JSON handling for NewPet to handle additionalProperties -func (a NewPet) MarshalJSON() ([]byte, error) {...}w -``` +## Examples -There are many special cases for `additionalProperties`, such as having to -define types for inner fields which themselves support additionalProperties, and -all of them are tested via the `internal/test/components` schemas and tests. Please -look through those tests for more usage examples. +The [examples directory](examples) contains some additional cases which are useful examples for how to use `oapi-codegen`, including how you'd take the Petstore API and implement it with `oapi-codegen`. -#### oneOf/anyOf/allOf support +You could also find some cases of how the project can be used by checking out our [internal test cases](internal/test) which are real-world usages that make up our regression tests. + +### Blog posts + +We love reading posts by the community about how to use the project. + +Here are a few we've found around the Web: + +- [Building a Go RESTful API with design-first OpenAPI contracts](https://www.jvt.me/posts/2022/07/12/go-openapi-server/) +- [A Practical Guide to Using oapi-codegen in Golang API Development with the Fiber Framework](https://medium.com/@fikihalan/a-practical-guide-to-using-oapi-codegen-in-golang-api-development-with-the-fiber-framework-bce2a59380ae) +- [Generating Go server code from OpenAPI 3 definitions](https://ldej.nl/post/generating-go-from-openapi-3/) +- [Go Client Code Generation from Swagger and OpenAPI](https://medium.com/@kyodo-tech/go-client-code-generation-from-swagger-and-openapi-a0576831836c) +- [Go oapi-codegen + request validation](https://blog.commitsmart.com/go-oapi-codegen-request-validation-285398b37dc8) +- [Streamlining Go + Chi Development: Generating Code from an OpenAPI Spec](https://i4o.dev/blog/oapi-codegen-with-chi-router) + +Got one to add? Please raise a PR! + +## Frequently Asked Questions (FAQs) + +### Does `oapi-codegen` support OpenAPI 3.1? + +No, we don't currently. + +OpenAPI 3.1 support is [awaiting upstream support](https://github.com/oapi-codegen/oapi-codegen/issues/373). + +In the meantime, you could follow [steps from this blog post](https://www.jvt.me/posts/2025/05/04/oapi-codegen-trick-openapi-3-1/) to [use OpenAPI Overlay](#modifying-the-input-openapi-specification-with-openapi-overlay) to "downgrade" the OpenAPI 3.1 spec to OpenAPI 3.0. + +### How does `oapi-codegen` handle `anyOf`, `allOf` and `oneOf`? + +`oapi-codegen` supports `anyOf`, `allOf` and `oneOf` for generated code. + +For instance, through the following OpenAPI spec: -- `oneOf` and `anyOf` are implemented using delayed parsing with the help of `json.RawMessage`. -The following schema will result in a type that has methods such as `AsCat`, `AsDog`, `FromCat`, `FromDog`, `MergeCat`, `MergeDog`. If the schema also includes a discriminator the generated code will also have methods such as `Discriminator`, `ValueByDiscriminator` and will force discriminator value in `From` methods. ```yaml -schema: - oneOf: - - $ref: '#/components/schemas/Cat' - - $ref: '#/components/schemas/Dog' +openapi: "3.0.0" +info: + version: 1.0.0 + title: Using complex schemas + description: An example of `anyOf`, `allOf` and `oneOf` +components: + schemas: + # base types + Client: + type: object + required: + - name + properties: + name: + type: string + Identity: + type: object + required: + - issuer + properties: + issuer: + type: string + + # allOf performs a union of all types defined + ClientWithId: + allOf: + - $ref: '#/components/schemas/Client' + - properties: + id: + type: integer + required: + - id + + # allOf performs a union of all types defined, but if there's a duplicate field defined, it'll be overwritten by the last schema + # https://github.com/oapi-codegen/oapi-codegen/issues/1569 + IdentityWithDuplicateField: + allOf: + # `issuer` will be ignored + - $ref: '#/components/schemas/Identity' + # `issuer` will be ignored + - properties: + issuer: + type: integer + # `issuer` will take precedence + - properties: + issuer: + type: object + properties: + name: + type: string + required: + - name + + # anyOf results in a type that has an `AsClient`/`MergeClient`/`FromClient` and an `AsIdentity`/`MergeIdentity`/`FromIdentity` method so you can choose which of them you want to retrieve + ClientAndMaybeIdentity: + anyOf: + - $ref: '#/components/schemas/Client' + - $ref: '#/components/schemas/Identity' + + # oneOf results in a type that has an `AsClient`/`MergeClient`/`FromClient` and an `AsIdentity`/`MergeIdentity`/`FromIdentity` method so you can choose which of them you want to retrieve + ClientOrIdentity: + oneOf: + - $ref: '#/components/schemas/Client' + - $ref: '#/components/schemas/Identity' ``` -- `allOf` is supported, by taking the union of all the fields in all the - component schemas. This is the most useful of these operations, and is - commonly used to merge objects with an identifier, as in the - `petstore-expanded` example. -## Generated Client Boilerplate +This results in the following types: -Once your server is up and running, you probably want to make requests to it. If -you're going to do those requests from your Go code, we also generate a client -which is conformant with your schema to help in marshaling objects to JSON. It -uses the same types and similar function signatures to your request handlers. +
-The interface for the pet store looks like this: +Base types ```go -// The interface specification for the client above. -type ClientInterface interface { +// Client defines model for Client. +type Client struct { + Name string `json:"name"` +} + +// Identity defines model for Identity. +type Identity struct { + Issuer string `json:"issuer"` +} +``` - // FindPets request - FindPets(ctx context.Context, params *FindPetsParams, reqEditors ...RequestEditorFn) (*http.Response, error) +
- // AddPet request with JSON body - AddPet(ctx context.Context, body NewPet, reqEditors ...RequestEditorFn) (*http.Response, error) +
- // DeletePet request - DeletePet(ctx context.Context, id int64, reqEditors ...RequestEditorFn) (*http.Response, error) +allOf - // FindPetById request - FindPetById(ctx context.Context, id int64, reqEditors ...RequestEditorFn) (*http.Response, error) +```go +// ClientWithId defines model for ClientWithId. +type ClientWithId struct { + Id int `json:"id"` + Name string `json:"name"` +} + +// IdentityWithDuplicateField defines model for IdentityWithDuplicateField. +type IdentityWithDuplicateField struct { + Issuer struct { + Name string `json:"name"` + } `json:"issuer"` } ``` -A Client object which implements the above interface is also generated: +
+ +
+ +anyOf ```go -// Client which conforms to the OpenAPI3 specification for this service. -type Client struct { - // The endpoint of the server conforming to this interface, with scheme, - // https://api.deepmap.com for example. - Server string +import ( + "encoding/json" - // HTTP client with any customized settings, such as certificate chains. - Client http.Client + "github.com/oapi-codegen/runtime" +) - // A callback for modifying requests which are generated before sending over - // the network. - RequestEditors []func(ctx context.Context, req *http.Request) error +// ClientAndMaybeIdentity defines model for ClientAndMaybeIdentity. +type ClientAndMaybeIdentity struct { + union json.RawMessage } -``` -Each operation in your OpenAPI spec will result in a client function which -takes the same arguments. It's difficult to handle any arbitrary body that -Swagger supports, so we've done some special casing for bodies, and you may get -more than one function for an operation with a request body. - -1) If you have more than one request body type, meaning more than one media - type, you will have a generic handler of this form: +// AsClient returns the union data inside the ClientAndMaybeIdentity as a Client +func (t ClientAndMaybeIdentity) AsClient() (Client, error) { + var body Client + err := json.Unmarshal(t.union, &body) + return body, err +} - AddPet(ctx context.Context, contentType string, body io.Reader) +// FromClient overwrites any union data inside the ClientAndMaybeIdentity as the provided Client +func (t *ClientAndMaybeIdentity) FromClient(v Client) error { + b, err := json.Marshal(v) + t.union = b + return err +} -2) If you have only a JSON request body, you will get: +// MergeClient performs a merge with any union data inside the ClientAndMaybeIdentity, using the provided Client +func (t *ClientAndMaybeIdentity) MergeClient(v Client) error { + b, err := json.Marshal(v) + if err != nil { + return err + } - AddPet(ctx context.Context, body NewPet) + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -3) If you have multiple request body types, which include a JSON type you will - get two functions. We've chosen to give the JSON version a shorter name, as - we work with JSON and don't want to wear out our keyboards. +// AsIdentity returns the union data inside the ClientAndMaybeIdentity as a Identity +func (t ClientAndMaybeIdentity) AsIdentity() (Identity, error) { + var body Identity + err := json.Unmarshal(t.union, &body) + return body, err +} - AddPet(ctx context.Context, body NewPet) - AddPetWithBody(ctx context.Context, contentType string, body io.Reader) +// FromIdentity overwrites any union data inside the ClientAndMaybeIdentity as the provided Identity +func (t *ClientAndMaybeIdentity) FromIdentity(v Identity) error { + b, err := json.Marshal(v) + t.union = b + return err +} -The Client object above is fairly flexible, since you can pass in your own -`http.Client` and a request editing callback. You can use that callback to add -headers. In our middleware stack, we annotate the context with additional -information such as the request ID and function tracing information, and we -use the callback to propagate that information into the request headers. Still, we -can't foresee all possible usages, so those functions call through to helper -functions which create requests. In the case of the pet store, we have: +// MergeIdentity performs a merge with any union data inside the ClientAndMaybeIdentity, using the provided Identity +func (t *ClientAndMaybeIdentity) MergeIdentity(v Identity) error { + b, err := json.Marshal(v) + if err != nil { + return err + } -```go -// Request generator for FindPets -func NewFindPetsRequest(server string, params *FindPetsParams) (*http.Request, error) {...} + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -// Request generator for AddPet with JSON body -func NewAddPetRequest(server string, body NewPet) (*http.Request, error) {...} +func (t ClientAndMaybeIdentity) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} -// Request generator for AddPet with non-JSON body -func NewAddPetRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) {...} +func (t *ClientAndMaybeIdentity) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} -// Request generator for DeletePet -func NewDeletePetRequest(server string, id int64) (*http.Request, error) {...} -// Request generator for FindPetById -func NewFindPetByIdRequest(server string, id int64) (*http.Request, error) {...} ``` -You can call these functions to build an `http.Request` from Go objects, which -will correspond to your request schema. They map one-to-one to the functions on -the client, except that we always generate the generic non-JSON body handler. +
-There are some caveats to using this code. -- exploded, form style query arguments, which are the default argument format - in OpenAPI 3.0 are undecidable. Say that I have two objects, one composed of - the fields `(name=bob, id=5)` and another which has `(name=shoe, color=brown)`. - The first parameter is named `person` and the second is named `item`. The - default marshaling style for query args would result in - `/path/?name=bob,id=5&name=shoe,color=brown`. In order to tell what belongs - to which object, we'd have to look at all the parameters and try to deduce it, - but we're lazy, so we didn't. Don't use exploded form style arguments if - you're passing around objects which have similar field names. If you - used unexploded form parameters, you'd have - `/path/?person=name,bob,id,5&item=name,shoe,color,brown`, which an be - parsed unambiguously. +
-- Parameters can be defined via `schema` or via `content`. Use the `content` form - for anything other than trivial objects, they can marshal to arbitrary JSON - structures. When you send them as cookie (`in: cookie`) arguments, we will - URL encode them, since JSON delimiters aren't allowed in cookies. +oneOf -## Using SecurityProviders +```go +// AsClient returns the union data inside the ClientOrIdentity as a Client +func (t ClientOrIdentity) AsClient() (Client, error) { + var body Client + err := json.Unmarshal(t.union, &body) + return body, err +} -If you generate client-code, you can use some default-provided security providers -which help you to use the various OpenAPI 3 Authentication mechanism. +// FromClient overwrites any union data inside the ClientOrIdentity as the provided Client +func (t *ClientOrIdentity) FromClient(v Client) error { + b, err := json.Marshal(v) + t.union = b + return err +} +// MergeClient performs a merge with any union data inside the ClientOrIdentity, using the provided Client +func (t *ClientOrIdentity) MergeClient(v Client) error { + b, err := json.Marshal(v) + if err != nil { + return err + } -```go - import ( - "github.com/deepmap/oapi-codegen/pkg/securityprovider" - ) + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - func CreateSampleProviders() error { - // Example BasicAuth - // See: https://swagger.io/docs/specification/authentication/basic-authentication/ - basicAuthProvider, basicAuthProviderErr := securityprovider.NewSecurityProviderBasicAuth("MY_USER", "MY_PASS") - if basicAuthProviderErr != nil { - panic(basicAuthProviderErr) - } +// AsIdentity returns the union data inside the ClientOrIdentity as a Identity +func (t ClientOrIdentity) AsIdentity() (Identity, error) { + var body Identity + err := json.Unmarshal(t.union, &body) + return body, err +} - // Example BearerToken - // See: https://swagger.io/docs/specification/authentication/bearer-authentication/ - bearerTokenProvider, bearerTokenProviderErr := securityprovider.NewSecurityProviderBearerToken("MY_TOKEN") - if bearerTokenProviderErr != nil { - panic(bearerTokenProviderErr) - } +// FromIdentity overwrites any union data inside the ClientOrIdentity as the provided Identity +func (t *ClientOrIdentity) FromIdentity(v Identity) error { + b, err := json.Marshal(v) + t.union = b + return err +} - // Example ApiKey provider - // See: https://swagger.io/docs/specification/authentication/api-keys/ - apiKeyProvider, apiKeyProviderErr := securityprovider.NewSecurityProviderApiKey("query", "myApiKeyParam", "MY_API_KEY") - if apiKeyProviderErr != nil { - panic(apiKeyProviderErr) - } +// MergeIdentity performs a merge with any union data inside the ClientOrIdentity, using the provided Identity +func (t *ClientOrIdentity) MergeIdentity(v Identity) error { + b, err := json.Marshal(v) + if err != nil { + return err + } - // Example providing your own provider using an anonymous function wrapping in the - // InterceptoFn adapter. The behaviour between the InterceptorFn and the Interceptor interface - // are the same as http.HandlerFunc and http.Handler. - customProvider := func(req *http.Request, ctx context.Context) error { - // Just log the request header, nothing else. - log.Println(req.Header) - return nil - } + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - // Exhaustive list of some defaults you can use to initialize a Client. - // If you need to override the underlying httpClient, you can use the option - // - // WithHTTPClient(httpClient *http.Client) - // - client, clientErr := NewClient("https://api.deepmap.com", WithRequestEditorFn(apiKeyProvider.Intercept)) - - return nil - } -``` - -## Extensions - -`oapi-codegen` supports the following extended properties: - -- `x-go-type`: specifies Go type name. It allows you to specify the type name for a schema, and - will override any default value. This extended property isn't supported in all parts of - OpenAPI, so please refer to the spec as to where it's allowed. Swagger validation tools will - flag incorrect usage of this property. -- `x-go-name`: specifies Go field name. It allows you to specify the field name for a schema, and - will override any default value. This extended property isn't supported in all parts of - OpenAPI, so please refer to the spec as to where it's allowed. Swagger validation tools will - flag incorrect usage of this property. -- `x-go-json-ignore`: sets tag to `-` to ignore the field in json completely. -- `x-oapi-codegen-extra-tags`: adds extra Go field tags to the generated struct field. This is - useful for interfacing with tag based ORM or validation libraries. The extra tags that - are added are in addition to the regular json tags that are generated. If you specify your - own `json` tag, you will override the default one. - - ```yaml - components: - schemas: - Object: - properties: - name: - type: string - x-oapi-codegen-extra-tags: - tag1: value1 - tag2: value2 - ``` - In the example above, field `name` will be declared as: - - ``` - Name string `json:"name" tag1:"value1" tag2:"value2"` - ``` -- `x-go-type-import`: adds extra Go imports to your generated code. It can help you, when you want to - choose your own import package for `x-go-type`. - - ```yaml - schemas: - Pet: - properties: - age: - x-go-type: myuuid.UUID - x-go-type-import: - name: myuuid - path: github.com/google/uuid - ``` - After code generation you will get this: - ```go - import ( - ... - myuuid "github.com/google/uuid" - ) - - //Pet defines model for Pet. - type Pet struct { - Age *myuuid.UUID `json:"age,omitempty"` - } - - ``` - `name` is an optional parameter. Example: - - ```yaml - components: - schemas: - Pet: - properties: - age: - x-go-type: uuid.UUID - x-go-type-import: - path: github.com/google/uuid - required: - - age - ``` +func (t ClientOrIdentity) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} - After code generation you will get this result: +func (t *ClientOrIdentity) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} +``` - ```go - import ( - "github.com/google/uuid" - ) +
- // Pet defines model for Pet. - type Pet struct { - Age uuid.UUID `json:"age"` - } - ``` +For more info, check out [the example code](examples/anyof-allof-oneof/). -- `x-enum-varnames`: supplies other enum names for the corresponding values. (alias: `x-enumNames`) +### How can I ignore parts of the spec I don't care about? - ```yaml - components: - schemas: - Object: - properties: - category: - type: integer - enum: [0, 1, 2] - x-enum-varnames: - - notice - - warning - - urgent - ``` - - After code generation you will get this result: - - ```go - // Defines values for ObjectCategory. - const ( - Notice ObjectCategory = 0 - Urgent ObjectCategory = 2 - Warning ObjectCategory = 1 - ) - - // ObjectCategory defines model for Object.Category. - type ObjectCategory int - ``` - -## Using `oapi-codegen` - -The default options for `oapi-codegen` will generate everything; client, server, -type definitions and embedded swagger spec, but you can generate subsets of -those via the `-generate` flag. It defaults to `types,client,server,spec`, but -you can specify any combination of those. - -- `types`: generate all type definitions for all types in the OpenAPI spec. This - will be everything under `#components`, as well as request parameter, request - body, and response type objects. -- `server`: generate the Echo server boilerplate. `server` requires the types in the - same package to compile. -- `chi-server`: generate the Chi server boilerplate. This code is dependent on - that produced by the `types` target. -- `client`: generate the client boilerplate. It, too, requires the types to be - present in its package. -- `spec`: embed the OpenAPI spec into the generated code as a gzipped blob. - This is then usable with the `OapiRequestValidator`, or to be used by other - methods that need access to the parsed OpenAPI specification -- `skip-fmt`: skip running `goimports` on the generated code. This is useful for debugging - the generated file in case the spec contains weird strings. -- `skip-prune`: skip pruning unused components from the spec prior to generating - the code. -- `import-mapping`: specifies a map of references external OpenAPI specs to go - Go include paths. Please see below. - -So, for example, if you would like to produce only the server code, you could -run `oapi-codegen -generate types,server`. You could generate `types` and -`server` into separate files, but both are required for the server code. - -`oapi-codegen` can filter paths base on their tags in the openapi definition. -Use either `-include-tags` or `-exclude-tags` followed by a comma-separated list -of tags. For instance, to generate a server that serves all paths except those -tagged with `auth` or `admin`, use the argument, `-exclude-tags="auth,admin"`. -To generate a server that only handles `admin` paths, use the argument -`-include-tags="admin"`. When neither of these arguments is present, all paths -are generated. - -`oapi-codegen` can filter schemas based on the option `--exclude-schemas`, which is -a comma separated list of schema names. For instance, `--exclude-schemas=Pet,NewPet` -will exclude from generation schemas `Pet` and `NewPet`. This allow to have a -in the same package a manually defined structure or interface and refer to it -in the openapi spec. - -Since `go generate` commands must be a single line, all the options above can make -them pretty unwieldy, so you can specify all of the options in a configuration -file via the `--config` option. Please see the test under -[`/internal/test/externalref/`](https://github.com/deepmap/oapi-codegen/blob/master/internal/test/externalref/externalref.cfg.yaml) -for an example. The structure of the file is as follows: - -```yaml -package: externalref -generate: - models: true - embedded-spec: true -import-mapping: - ./packageA/spec.yaml: github.com/deepmap/oapi-codegen/internal/test/externalref/packageA - ./packageB/spec.yaml: github.com/deepmap/oapi-codegen/internal/test/externalref/packageB -output: externalref.gen.go +By default, `oapi-codegen` will generate everything from the specification. + +If you'd like to reduce what's generated, you can use one of a few options in [the configuration file](#usage) to tune the generation of the resulting output: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json output-options: - skip-prune: true + include-tags: [] + exclude-tags: [] + include-operation-ids: [] + exclude-operation-ids: [] + exclude-schemas: [] ``` -Have a look at [`cmd/oapi-codegen/oapi-codegen.go`](https://github.com/deepmap/oapi-codegen/blob/master/cmd/oapi-codegen/oapi-codegen.go#L48) -to see all the fields on the configuration structure. +Check [the docs](https://pkg.go.dev/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen#OutputOptions) for more details of usage. + +### Should I commit the generated code? + +We recommend doing so, yes, for the following reasons: + +- It means it's easier to view the impact of a change - be it due to an upgrade of `oapi-codegen`, or a change to your spec - and has helped catch (possibly) breaking changes in the past more easily +- It then allows your codebase to be consumed as a library, as all the files are committed + +This means you'll need to have your CI/CD pipeline validate that generated files are all up-to-date, but that's a fairly straightforward piece of work. -### Import Mappings +### Should I lint the generated code? -OpenAPI specifications may contain references to other OpenAPI specifications, -and we need some additional information in order to be able to generate correct -Go code. +We really ask that you don't. Although it intends to be idiomatic Go code, it's not expected to pass all the various linting rules that your project may apply. -An external reference looks like this: +> [!NOTE] +> We will, on occasion, improve the generated code to fix some linting warnings, such as those from `go vet`, but this should not be an expected change. - $ref: ./some_spec.yaml#/components/schemas/Type +### I've just updated my version of `kin-openapi`, and now I can't build my code 😠 -We assume that you have already generated the boilerplate code for `./some_spec.yaml` -using `oapi-codegen`, and you have a package which contains the generated code, -let's call it `github.com/deepmap/some-package`. You need to tell `oapi-codegen` that -`some_spec.yaml` corresponds to this package, and you would do it by specifying -this command line argument: +The [kin-openapi](https://github.com/getkin/kin-openapi) project - which we 💜 for providing a great library and set of tooling for interacting with OpenAPI - is a pre-v1 release, which means that they're within their rights to push breaking changes. - -import-mapping=./some_spec.yaml:github.com/deepmap/some-package +This may lead to breakage in your consuming code, and if so, sorry that's happened! -This tells us that in order to resolve references generated from `some_spec.yaml` we -need to import `github.com/deepmap/some-package`. You may specify multiple mappings -by comma separating them in the form `key1:value1,key2:value2`. +We'll be aware of the issue, and will work to update both the core `oapi-codegen` and the middlewares accordingly. -## What's missing or incomplete +## Contributors -This code is still young, and not complete, since we're filling it in as we -need it. We've not yet implemented several things: +We're very appreciative of [the many contributors over the years](https://github.com/oapi-codegen/oapi-codegen/graphs/contributors) and the ongoing use of the project 💜 -- `patternProperties` isn't yet supported and will exit with an error. Pattern - properties were defined in JSONSchema, and the `kin-openapi` Swagger object - knows how to parse them, but they're not part of OpenAPI 3.0, so we've left - them out, as support is very complicated. + + + +## Sponsors -## Making changes to code generation +For the most part, `oapi-codegen` is maintained in two busy peoples' free time. As noted in [Creating a more sustainable model for `oapi-codegen` in the future](https://github.com/oapi-codegen/oapi-codegen/discussions/1606), we're looking to make this a more sustainable project in the future. -The code generator uses a tool to inline all the template definitions into -code, so that we don't have to deal with the location of the template files. -When you update any of the files under the `templates/` directory, you will -need to regenerate the template inlines: +Please consider sponsoring us through GitHub Sponsors either [on the organisation](https://github.com/sponsors/oapi-codegen/) or [directly for Jamie](https://github.com/sponsors/jamietanna/), which helps work towards us being able to maintain the project long term. - go generate ./pkg/codegen/templates +See [this blog post from Tidelift](https://blog.tidelift.com/paying-maintainers-the-howto) for more details on how to talk to your company about sponsoring maintainers of (Open Source) projects you depend on. -All this command does is inline the files ending in `.tmpl` into the specified -Go file. +We are also generously sponsored by the following folks, each of whom provide sponsorship for 1 hour of work a month: -Afterwards you should run `go generate ./...`, and the templates will be updated - accordingly. +

+ + + + + DevZero logo + + +

-Alternatively, you can provide custom templates to override built-in ones using -the `-templates` flag specifying a path to a directory containing templates -files. These files **must** be named identically to built-in template files -(see `pkg/codegen/templates/*.tmpl` in the source code), and will be interpreted -on-the-fly at run time. Example: +

+ + Cybozu logo + +

- $ ls -1 my-templates/ - client.tmpl - typedef.tmpl - $ oapi-codegen \ - -templates my-templates/ \ - -generate types,client \ - petstore-expanded.yaml +(Note that the order of appearance the order in which sponsorship was received) diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000000..41fd214d54 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,41 @@ +# Support model + +[`oapi-codegen`](https://github.com/oapi-codegen/oapi-codegen) is currently supported in a best-efforts means, due to the [Core Maintainers](https://github.com/oapi-codegen/governance/#core-maintainer) working in their "off hours" from their busy jobs. + +We do thoroughly appreciate our users, the feature requests and bug reports raised, and want to set expectations accordingly. + +Related: + +- [Creating a more sustainable model for `oapi-codegen` in the future](https://github.com/oapi-codegen/oapi-codegen/discussions/1606) +- [Looking back at `oapi-codegen`'s last year](https://github.com/oapi-codegen/oapi-codegen/discussions/1985) + +## Supported versions + +Only the latest minor release version of `oapi-codegen` is supported for active development. + +`oapi-codegen` does not currently backport any bug fixes. + +## Security updates + +Related: [`oapi-codegen`'s organisational security policy (`SECURITY.md`)](https://github.com/oapi-codegen/.github/blob/HEAD/SECURITY.md). + +## Minimum required Go toolchain version + +As per [the install instructions](https://github.com/oapi-codegen/oapi-codegen/#install), `oapi-codegen`'s recommended installation model is as a source-tracked dependency, for instance using `go tool`. + +Because of this, we take more care to resist bumping the `go` directive, as it has a knock-on effect for all consumers of `oapi-codegen`, as it requires the consumer to _also_ bump their `go` directive. + +When considering whether to bump the `go` directive, we will consider: + +- Do we _definitely_ need to pull in this new version of Go? + - Can we work around it by not using new language features? +- If this is a requirement of an upstream dependency, can upstream use build tags ([like so](https://github.com/charmbracelet/log/pull/13)), to allow us to continue using old versions of Go? + - If it is a requirement, and we don't want to bump it (yet), can we avoid the Go version bump? +- Is the new version supported by the Go team? + - We're comfortable not requiring the Go version being in active support - it's up to consumers to decide what toolchain + standard library version they want to use to build + +We will not mandate a `toolchain` directive. + +## Additional support + +For additional support, it's worth reading [oapi-codegen/governance: Sponsorship](https://github.com/oapi-codegen/governance/#sponsorship), and visiting the different [funding options](https://github.com/oapi-codegen/oapi-codegen/blob/main/.github/FUNDING.yml). diff --git a/cmd/oapi-codegen/oapi-codegen.go b/cmd/oapi-codegen/oapi-codegen.go index aa33a76ea7..088821f5da 100644 --- a/cmd/oapi-codegen/oapi-codegen.go +++ b/cmd/oapi-codegen/oapi-codegen.go @@ -25,11 +25,14 @@ import ( "gopkg.in/yaml.v2" - "github.com/deepmap/oapi-codegen/pkg/codegen" - "github.com/deepmap/oapi-codegen/pkg/util" + "github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen" + "github.com/oapi-codegen/oapi-codegen/v2/pkg/util" ) -func errExit(format string, args ...interface{}) { +func errExit(format string, args ...any) { + if !strings.HasSuffix(format, "\n") { + format = format + "\n" + } _, _ = fmt.Fprintf(os.Stderr, format, args...) os.Exit(1) } @@ -42,17 +45,20 @@ var ( flagPrintVersion bool flagPackageName string flagPrintUsage bool + flagGenerate string + flagTemplatesDir string - // The options below are deprecated, and they will be removed in a future + // Deprecated: The options below will be removed in a future // release. Please use the new config file format. - flagGenerate string - flagIncludeTags string - flagExcludeTags string - flagTemplatesDir string - flagImportMapping string - flagExcludeSchemas string - flagResponseTypeSuffix string - flagAliasTypes bool + flagIncludeTags string + flagExcludeTags string + flagIncludeOperationIDs string + flagExcludeOperationIDs string + flagImportMapping string + flagExcludeSchemas string + flagResponseTypeSuffix string + flagAliasTypes bool + flagInitialismOverrides bool ) type configuration struct { @@ -65,39 +71,48 @@ type configuration struct { // oldConfiguration is deprecated. Please add no more flags here. It is here // for backwards compatibility, and it will be removed in the future. type oldConfiguration struct { - PackageName string `yaml:"package"` - GenerateTargets []string `yaml:"generate"` - OutputFile string `yaml:"output"` - IncludeTags []string `yaml:"include-tags"` - ExcludeTags []string `yaml:"exclude-tags"` - TemplatesDir string `yaml:"templates"` - ImportMapping map[string]string `yaml:"import-mapping"` - ExcludeSchemas []string `yaml:"exclude-schemas"` - ResponseTypeSuffix string `yaml:"response-type-suffix"` - Compatibility codegen.CompatibilityOptions `yaml:"compatibility"` + PackageName string `yaml:"package"` + GenerateTargets []string `yaml:"generate"` + OutputFile string `yaml:"output"` + IncludeTags []string `yaml:"include-tags"` + ExcludeTags []string `yaml:"exclude-tags"` + IncludeOperationIDs []string `yaml:"include-operation-ids"` + ExcludeOperationIDs []string `yaml:"exclude-operation-ids"` + TemplatesDir string `yaml:"templates"` + ImportMapping map[string]string `yaml:"import-mapping"` + ExcludeSchemas []string `yaml:"exclude-schemas"` + ResponseTypeSuffix string `yaml:"response-type-suffix"` + Compatibility codegen.CompatibilityOptions `yaml:"compatibility"` } +// noVCSVersionOverride allows overriding the version of the application for cases where no Version Control System (VCS) is available when building, for instance when using a Nix derivation. +// See documentation for how to use it in examples/no-vcs-version-override/README.md +var noVCSVersionOverride string + func main() { - flag.StringVar(&flagOutputFile, "o", "", "Where to output generated code, stdout is default") - flag.BoolVar(&flagOldConfigStyle, "old-config-style", false, "whether to use the older style config file format") - flag.BoolVar(&flagOutputConfig, "output-config", false, "when true, outputs a configuration file for oapi-codegen using current settings") - flag.StringVar(&flagConfigFile, "config", "", "a YAML config file that controls oapi-codegen behavior") - flag.BoolVar(&flagPrintVersion, "version", false, "when specified, print version and exit") - flag.StringVar(&flagPackageName, "package", "", "The package name for generated code") - flag.BoolVar(&flagPrintUsage, "help", false, "show this help and exit") - flag.BoolVar(&flagPrintUsage, "h", false, "same as -help") + flag.StringVar(&flagOutputFile, "o", "", "Where to output generated code, stdout is default.") + flag.BoolVar(&flagOldConfigStyle, "old-config-style", false, "Whether to use the older style config file format.") + flag.BoolVar(&flagOutputConfig, "output-config", false, "When true, outputs a configuration file for oapi-codegen using current settings.") + flag.StringVar(&flagConfigFile, "config", "", "A YAML config file that controls oapi-codegen behavior.") + flag.BoolVar(&flagPrintVersion, "version", false, "When specified, print version and exit.") + flag.StringVar(&flagPackageName, "package", "", "The package name for generated code.") + flag.BoolVar(&flagPrintUsage, "help", false, "Show this help and exit.") + flag.BoolVar(&flagPrintUsage, "h", false, "Same as -help.") // All flags below are deprecated, and will be removed in a future release. Please do not // update their behavior. flag.StringVar(&flagGenerate, "generate", "types,client,server,spec", - `Comma-separated list of code to generate; valid options: "types", "client", "chi-server", "server", "gin", "gorilla", "spec", "skip-fmt", "skip-prune"`) + `Comma-separated list of code to generate; valid options: "types", "client", "chi-server", "server", "gin", "gorilla", "spec", "skip-fmt", "skip-prune", "fiber", "iris", "std-http".`) flag.StringVar(&flagIncludeTags, "include-tags", "", "Only include operations with the given tags. Comma-separated list of tags.") flag.StringVar(&flagExcludeTags, "exclude-tags", "", "Exclude operations that are tagged with the given tags. Comma-separated list of tags.") - flag.StringVar(&flagTemplatesDir, "templates", "", "Path to directory containing user templates") - flag.StringVar(&flagImportMapping, "import-mapping", "", "A dict from the external reference to golang package path") - flag.StringVar(&flagExcludeSchemas, "exclude-schemas", "", "A comma separated list of schemas which must be excluded from generation") - flag.StringVar(&flagResponseTypeSuffix, "response-type-suffix", "", "the suffix used for responses types") - flag.BoolVar(&flagAliasTypes, "alias-types", false, "Alias type declarations of possible") + flag.StringVar(&flagIncludeOperationIDs, "include-operation-ids", "", "Only include operations with the given operation-ids. Comma-separated list of operation-ids.") + flag.StringVar(&flagExcludeOperationIDs, "exclude-operation-ids", "", "Exclude operations with the given operation-ids. Comma-separated list of operation-ids.") + flag.StringVar(&flagTemplatesDir, "templates", "", "Path to directory containing user templates.") + flag.StringVar(&flagImportMapping, "import-mapping", "", "A dict from the external reference to golang package path.") + flag.StringVar(&flagExcludeSchemas, "exclude-schemas", "", "A comma separated list of schemas which must be excluded from generation.") + flag.StringVar(&flagResponseTypeSuffix, "response-type-suffix", "", "The suffix used for responses types.") + flag.BoolVar(&flagAliasTypes, "alias-types", false, "Alias type declarations if possible.") + flag.BoolVar(&flagInitialismOverrides, "initialism-overrides", false, "Use initialism overrides.") flag.Parse() @@ -113,7 +128,11 @@ func main() { os.Exit(1) } fmt.Println(bi.Main.Path + "/cmd/oapi-codegen") - fmt.Println(bi.Main.Version) + version := bi.Main.Version + if len(noVCSVersionOverride) > 0 { + version = noVCSVersionOverride + } + fmt.Println(version) return } @@ -153,7 +172,7 @@ func main() { t := true oldConfigStyle = &t } else if oldErr != nil && newErr != nil { - errExit("error parsing configuration style as old version or new version: %v\n", err) + errExit("error parsing configuration style as old version or new version\n\nerror when parsing using old config version:\n%v\n\nerror when parsing using new config version:\n%v\n", oldErr, newErr) } // Else we fall through, and we still don't know, so we need to infer it from flags. } @@ -164,10 +183,8 @@ func main() { // config style. It should work correctly if we go down the old path, // even if we have a simple config file readable as both types. deprecatedFlagNames := map[string]bool{ - "generate": true, "include-tags": true, "exclude-tags": true, - "templates": true, "import-mapping": true, "exclude-schemas": true, "response-type-suffix": true, @@ -216,9 +233,8 @@ func main() { OutputFile: flagOutputFile, } } - var err error - opts, err = updateConfigFromFlags(opts) - if err != nil { + + if err := updateConfigFromFlags(&opts); err != nil { errExit("error processing flags: %v\n", err) } } else { @@ -233,7 +249,13 @@ func main() { errExit("error parsing'%s' as YAML: %v\n", flagConfigFile, err) } } - opts = newConfigFromOldConfig(oldConfig) + var err error + opts, err = newConfigFromOldConfig(oldConfig) + if err != nil { + flag.PrintDefaults() + errExit("error creating new config from old config: %v\n", err) + } + } // Ensure default values are set if user hasn't specified some needed @@ -249,6 +271,16 @@ func main() { errExit("configuration error: %v\n", err) } + if warnings := opts.Generate.Warnings(); len(warnings) > 0 { + var out strings.Builder + out.WriteString("WARNING: A number of warning(s) were returned when validating the GenerateOptions:") + for k, v := range warnings { + out.WriteString("\n- " + k + ": " + v) + } + + _, _ = fmt.Fprint(os.Stderr, out.String()) + } + // If the user asked to output configuration, output it to stdout and exit if flagOutputConfig { buf, err := yaml.Marshal(opts) @@ -259,9 +291,27 @@ func main() { return } - swagger, err := util.LoadSwagger(flag.Arg(0)) + overlayOpts := util.LoadSwaggerWithOverlayOpts{ + Path: opts.OutputOptions.Overlay.Path, + // default to strict, but can be overridden + Strict: true, + } + + if opts.OutputOptions.Overlay.Strict != nil { + overlayOpts.Strict = *opts.OutputOptions.Overlay.Strict + } + + swagger, err := util.LoadSwaggerWithOverlay(flag.Arg(0), overlayOpts) if err != nil { - errExit("error loading swagger spec in %s\n: %s", flag.Arg(0), err) + errExit("error loading swagger spec in %s\n: %s\n", flag.Arg(0), err) + } + + if strings.HasPrefix(swagger.OpenAPI, "3.1.") { + fmt.Fprintln(os.Stderr, "WARNING: You are using an OpenAPI 3.1.x specification, which is not yet supported by oapi-codegen (https://github.com/oapi-codegen/oapi-codegen/issues/373) and so some functionality may not be available. Until oapi-codegen supports OpenAPI 3.1, it is recommended to downgrade your spec to 3.0.x") + } + + if len(noVCSVersionOverride) > 0 { + opts.NoVCSVersionOverride = &noVCSVersionOverride } code, err := codegen.Generate(swagger, opts.Configuration) @@ -270,7 +320,10 @@ func main() { } if opts.OutputFile != "" { - err = os.WriteFile(opts.OutputFile, []byte(code), 0644) + if err := os.MkdirAll(filepath.Dir(opts.OutputFile), 0o755); err != nil { + errExit("error unable to create directory: %s\n", err) + } + err = os.WriteFile(opts.OutputFile, []byte(code), 0o644) if err != nil { errExit("error writing generated code to file: %s\n", err) } @@ -280,7 +333,7 @@ func main() { } func loadTemplateOverrides(templatesDir string) (map[string]string, error) { - var templates = make(map[string]string) + templates := make(map[string]string) if templatesDir == "" { return templates, nil @@ -294,7 +347,7 @@ func loadTemplateOverrides(templatesDir string) (map[string]string, error) { for _, f := range files { // Recursively load subdirectory files, using the path relative to the templates // directory as the key. This allows for overriding the files in the service-specific - // directories (e.g. echo, chi, etc.). + // directories (e.g. echo, chi, fiber, etc.). if f.IsDir() { subFiles, err := loadTemplateOverrides(path.Join(templatesDir, f.Name())) if err != nil { @@ -352,51 +405,63 @@ func detectPackageName(cfg *configuration) error { } // updateConfigFromFlags updates a loaded configuration from flags. Flags -// override anything in the file. We generate errors for command line options -// associated with the old style configuration -func updateConfigFromFlags(cfg configuration) (configuration, error) { +// override anything in the file. We generate errors for any unsupported +// command line flags. +func updateConfigFromFlags(cfg *configuration) error { if flagPackageName != "" { cfg.PackageName = flagPackageName } - var unsupportedFlags []string - if flagGenerate != "types,client,server,spec" { - unsupportedFlags = append(unsupportedFlags, "--generate") + // Override generation and output options from generate command line flag. + if err := generationTargets(&cfg.Configuration, util.ParseCommandLineList(flagGenerate)); err != nil { + return err + } } if flagIncludeTags != "" { - unsupportedFlags = append(unsupportedFlags, "--include-tags") + cfg.OutputOptions.IncludeTags = util.ParseCommandLineList(flagIncludeTags) } if flagExcludeTags != "" { - unsupportedFlags = append(unsupportedFlags, "--exclude-tags") + cfg.OutputOptions.ExcludeTags = util.ParseCommandLineList(flagExcludeTags) + } + if flagIncludeOperationIDs != "" { + cfg.OutputOptions.IncludeOperationIDs = util.ParseCommandLineList(flagIncludeOperationIDs) } + if flagExcludeOperationIDs != "" { + cfg.OutputOptions.ExcludeOperationIDs = util.ParseCommandLineList(flagExcludeOperationIDs) + } + if flagTemplatesDir != "" { - unsupportedFlags = append(unsupportedFlags, "--templates") + templates, err := loadTemplateOverrides(flagTemplatesDir) + if err != nil { + return fmt.Errorf("load templates from %q: %w", flagTemplatesDir, err) + } + cfg.OutputOptions.UserTemplates = templates } if flagImportMapping != "" { - unsupportedFlags = append(unsupportedFlags, "--import-mapping") + var err error + cfg.ImportMapping, err = util.ParseCommandlineMap(flagImportMapping) + if err != nil { + return err + } } if flagExcludeSchemas != "" { - unsupportedFlags = append(unsupportedFlags, "--exclude-schemas") + cfg.OutputOptions.ExcludeSchemas = util.ParseCommandLineList(flagExcludeSchemas) } if flagResponseTypeSuffix != "" { - unsupportedFlags = append(unsupportedFlags, "--response-type-suffix") + cfg.OutputOptions.ResponseTypeSuffix = flagResponseTypeSuffix } if flagAliasTypes { - unsupportedFlags = append(unsupportedFlags, "--alias-types") - } - - if len(unsupportedFlags) > 0 { - return configuration{}, fmt.Errorf("flags %s aren't supported in "+ - "new config style, please use --old-config-style or update your configuration ", - strings.Join(unsupportedFlags, ", ")) + return fmt.Errorf("--alias-types isn't supported any more") } if cfg.OutputFile == "" { cfg.OutputFile = flagOutputFile } - return cfg, nil + cfg.OutputOptions.InitialismOverrides = flagInitialismOverrides + + return nil } // updateOldConfigFromFlags parses the flags and the config file. Anything which is @@ -434,7 +499,49 @@ func updateOldConfigFromFlags(cfg oldConfiguration) oldConfiguration { return cfg } -func newConfigFromOldConfig(c oldConfiguration) configuration { +// generationTargets sets cfg options based on the generation targets. +func generationTargets(cfg *codegen.Configuration, targets []string) error { + opts := codegen.GenerateOptions{} // Blank to start with. + for _, opt := range targets { + switch opt { + case "iris", "iris-server": + opts.IrisServer = true + case "chi-server", "chi": + opts.ChiServer = true + case "fiber-server", "fiber": + opts.FiberServer = true + case "server", "echo-server", "echo": + opts.EchoServer = true + case "echo5", "echo5-server": + opts.Echo5Server = true + case "gin", "gin-server": + opts.GinServer = true + case "gorilla", "gorilla-server": + opts.GorillaServer = true + case "std-http", "std-http-server": + opts.StdHTTPServer = true + case "strict-server": + opts.Strict = true + case "client": + opts.Client = true + case "types", "models": + opts.Models = true + case "spec", "embedded-spec": + opts.EmbeddedSpec = true + case "skip-fmt": + cfg.OutputOptions.SkipFmt = true + case "skip-prune": + cfg.OutputOptions.SkipPrune = true + default: + return fmt.Errorf("unknown generate option %q", opt) + } + } + cfg.Generate = opts + + return nil +} + +func newConfigFromOldConfig(c oldConfiguration) (configuration, error) { // Take flags into account. cfg := updateOldConfigFromFlags(c) @@ -445,33 +552,8 @@ func newConfigFromOldConfig(c oldConfiguration) configuration { } opts.OutputOptions.ResponseTypeSuffix = flagResponseTypeSuffix - for _, g := range cfg.GenerateTargets { - switch g { - case "client": - opts.Generate.Client = true - case "chi-server": - opts.Generate.ChiServer = true - case "server": - opts.Generate.EchoServer = true - case "gin": - opts.Generate.GinServer = true - case "gorilla": - opts.Generate.GorillaServer = true - case "strict-server": - opts.Generate.Strict = true - case "types": - opts.Generate.Models = true - case "spec": - opts.Generate.EmbeddedSpec = true - case "skip-fmt": - opts.OutputOptions.SkipFmt = true - case "skip-prune": - opts.OutputOptions.SkipPrune = true - default: - fmt.Printf("unknown generate option %s\n", g) - flag.PrintDefaults() - os.Exit(1) - } + if err := generationTargets(&opts, cfg.GenerateTargets); err != nil { + return configuration{}, fmt.Errorf("generation targets: %w", err) } opts.OutputOptions.IncludeTags = cfg.IncludeTags @@ -480,7 +562,7 @@ func newConfigFromOldConfig(c oldConfiguration) configuration { templates, err := loadTemplateOverrides(cfg.TemplatesDir) if err != nil { - errExit("error loading template overrides: %s\n", err) + return configuration{}, fmt.Errorf("loading template overrides: %w", err) } opts.OutputOptions.UserTemplates = templates @@ -491,5 +573,5 @@ func newConfigFromOldConfig(c oldConfiguration) configuration { return configuration{ Configuration: opts, OutputFile: cfg.OutputFile, - } + }, nil } diff --git a/cmd/oapi-codegen/oapi-codegen_test.go b/cmd/oapi-codegen/oapi-codegen_test.go index 06992d9f73..f60dbb9afb 100644 --- a/cmd/oapi-codegen/oapi-codegen_test.go +++ b/cmd/oapi-codegen/oapi-codegen_test.go @@ -3,14 +3,14 @@ package main import ( "testing" - "github.com/deepmap/oapi-codegen/pkg/util" + "github.com/oapi-codegen/oapi-codegen/v2/pkg/util" ) func TestLoader(t *testing.T) { paths := []string{ "../../examples/petstore-expanded/petstore-expanded.yaml", - "https://petstore3.swagger.io/api/v3/openapi.json", + "https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/v2.4.1/examples/petstore-expanded/petstore-expanded.yaml", } for _, v := range paths { diff --git a/configuration-schema.json b/configuration-schema.json new file mode 100644 index 0000000000..d25061e31b --- /dev/null +++ b/configuration-schema.json @@ -0,0 +1,353 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Configuration files for oapi-codegen", + "type": "object", + "additionalProperties": false, + "properties": { + "package": { + "type": "string", + "description": "Go package name to generate the code under" + }, + "generate": { + "type": "object", + "additionalProperties": false, + "description": "Generate specifies which supported output formats to generate", + "properties": { + "iris-server": { + "type": "boolean", + "description": "IrisServer specifies whether to generate iris server boilerplate" + }, + "chi-server": { + "type": "boolean", + "description": "ChiServer specifies whether to generate chi server boilerplate" + }, + "fiber-server": { + "type": "boolean", + "description": "FiberServer specifies whether to generate fiber server boilerplate" + }, + "echo-server": { + "type": "boolean", + "description": "EchoServer specifies whether to generate echo server boilerplate" + }, + "echo5-server": { + "type": "boolean", + "description": "Echo5Server specifies whether to generate echo v5 server boilerplate" + }, + "gin-server": { + "type": "boolean", + "description": "GinServer specifies whether to generate gin server boilerplate" + }, + "gorilla-server": { + "type": "boolean", + "description": "GorillaServer specifies whether to generate Gorilla server boilerplate" + }, + "std-http-server": { + "type": "boolean", + "description": "StdHTTPServer specifies whether to generate stdlib http server boilerplate" + }, + "strict-server": { + "type": "boolean", + "description": "Strict specifies whether to generate strict server wrapper" + }, + "client": { + "type": "boolean", + "description": "Client specifies whether to generate client boilerplate" + }, + "models": { + "type": "boolean", + "description": "Models specifies whether to generate type definitions" + }, + "embedded-spec": { + "type": "boolean", + "description": "EmbeddedSpec indicates whether to embed the swagger spec in the generated code" + }, + "server-urls": { + "type": "boolean", + "description": "Generate types for the `Server` definitions' URLs, instead of needing to provide your own values" + } + } + }, + "compatibility": { + "type": "object", + "additionalProperties": false, + "description": "", + "properties": { + "old-merge-schemas": { + "type": "boolean", + "description": "In the past, we merged schemas for `allOf` by inlining each schema within the schema list. This approach, though, is incorrect because `allOf` merges at the schema definition level, not at the resulting model level. So, new behavior merges OpenAPI specs but generates different code than we have in the past. Set OldMergeSchemas to true for the old behavior. Please see https://github.com/oapi-codegen/oapi-codegen/issues/531" + }, + "old-enum-conflicts": { + "type": "boolean", + "description": "Enum values can generate conflicting typenames, so we've updated the code for enum generation to avoid these conflicts, but it will result in some enum types being renamed in existing code. Set OldEnumConflicts to true to revert to old behavior. Please see: Please see https://github.com/oapi-codegen/oapi-codegen/issues/549" + }, + "old-aliasing": { + "type": "boolean", + "description": "It was a mistake to generate a go type definition for every $ref in the OpenAPI schema. New behavior uses type aliases where possible, but this can generate code which breaks existing builds. Set OldAliasing to true for old behavior. Please see https://github.com/oapi-codegen/oapi-codegen/issues/549" + }, + "disable-flatten-additional-properties": { + "type": "boolean", + "description": "When an object contains no members, and only an additionalProperties specification, it is flattened to a map" + }, + "disable-required-readonly-as-pointer": { + "type": "boolean", + "description": "When an object property is both required and readOnly the go model is generated as a pointer. Set DisableRequiredReadOnlyAsPointer to true to mark them as non pointer. Please see https://github.com/oapi-codegen/oapi-codegen/issues/604" + }, + "always-prefix-enum-values": { + "type": "boolean", + "description": "When set to true, always prefix enum values with their type name instead of only when typenames would be conflicting." + }, + "apply-chi-middleware-first-to-last": { + "type": "boolean", + "description": "Our generated code for Chi has historically inverted the order in which Chi middleware is applied such that the last invoked middleware ends up executing first in the Chi chain This resolves the behavior such that middlewares are chained in the order they are invoked. Please see https://github.com/oapi-codegen/oapi-codegen/issues/786" + }, + "apply-gorilla-middleware-first-to-last": { + "type": "boolean", + "description": "Our generated code for gorilla/mux has historically inverted the order in which gorilla/mux middleware is applied such that the last invoked middleware ends up executing first in the middlewares chain This resolves the behavior such that middlewares are chained in the order they are invoked. Please see https://github.com/oapi-codegen/oapi-codegen/issues/841" + }, + "circular-reference-limit": { + "type": "integer", + "description": "DEPRECATED: No longer used.\nCircularReferenceLimit allows controlling the limit for circular reference checking. In some OpenAPI specifications, we have a higher number of circular references than is allowed out-of-the-box, but can be tuned to allow traversing them." + }, + "allow-unexported-struct-field-names": { + "type": "boolean", + "description": "AllowUnexportedStructFieldNames makes it possible to output structs that have fields that are unexported.\nThis is expected to be used in conjunction with an extension such as `x-go-name` to override the output name, and `x-oapi-codegen-extra-tags` to not produce JSON tags for `encoding/json`.\nNOTE that this can be confusing to users of your OpenAPI specification, who may see a field present and therefore be expecting to see it in the response, without understanding the nuance of how `oapi-codegen` generates the code." + }, + "preserve-original-operation-id-casing-in-embedded-spec": { + "type": "boolean", + "description": "When `oapi-codegen` parses the original OpenAPI specification, it will apply the configured `output-options.name-normalizer` to each operation's `operationId` before that is used to generate code from.\nHowever, this is also applied to the copy of the `operationId`s in the `embedded-spec` generation, which means that the embedded OpenAPI specification is then out-of-sync with the input specificiation.\nTo ensure that the `operationId` in the embedded spec is preserved as-is from the input specification, set this. NOTE that this will not impact generated code.\nNOTE that if you're using `include-operation-ids` or `exclude-operation-ids` you may want to ensure that the `operationId`s used are correct." + } + } + }, + "output-options": { + "type": "object", + "additionalProperties": false, + "description": "OutputOptions are used to modify the output code in some way", + "properties": { + "skip-fmt": { + "type": "boolean", + "description": "Whether to skip go imports on the generated code" + }, + "skip-prune": { + "type": "boolean", + "description": "Whether to skip pruning unused components on the generated code" + }, + "include-tags": { + "type": "array", + "description": "Only include operations that have one of these tags. Ignored when empty.", + "items": { + "type": "string" + } + }, + "exclude-tags": { + "type": "array", + "description": "Exclude operations that have one of these tags. Ignored when empty.", + "items": { + "type": "string" + } + }, + "include-operation-ids": { + "type": "array", + "description": "Only include operations that have one of these operation-ids. Ignored when empty.", + "items": { + "type": "string" + } + }, + "exclude-operation-ids": { + "type": "array", + "description": "Exclude operations that have one of these operation-ids. Ignored when empty.", + "items": { + "type": "string" + } + }, + "user-templates": { + "type": "object", + "description": "Override built-in templates from user-provided files", + "additionalProperties": { + "type": "string" + } + }, + "exclude-schemas": { + "type": "array", + "description": "Exclude from generation schemas with given names. Ignored when empty.", + "items": { + "type": "string" + } + }, + "response-type-suffix": { + "type": "string", + "description": "The suffix used for responses types" + }, + "client-type-name": { + "type": "string", + "description": "Override the default generated client type with the value" + }, + "initialism-overrides": { + "type": "boolean", + "description": "Whether to use the initialism overrides" + }, + "additional-initialisms": { + "type": "array", + "description": "AdditionalInitialisms defines additional initialisms to be used by the code generator. Has no effect unless the `name-normalizer` is set to `ToCamelCaseWithInitialisms`", + "items": { + "type": "string" + } + }, + "nullable-type": { + "type": "boolean", + "description": "Whether to generate nullable type for nullable fields" + }, + "disable-type-aliases-for-type": { + "type": "array", + "description": "DisableTypeAliasesForType allows defining which OpenAPI `type`s will explicitly not use type aliases", + "items": { + "type": "string", + "enum": ["array"] + } + }, + "name-normalizer": { + "type": "string", + "description": "NameNormalizer is the method used to normalize Go names and types, for instance converting the text `MyApi` to `MyAPI`. Corresponds with the constants defined for `codegen.NameNormalizerFunction`", + "default": "ToCamelCase", + "enum": [ + "ToCamelCase", + "ToCamelCaseWithDigits", + "ToCamelCaseWithInitialisms" + ] + }, + "overlay": { + "type": "object", + "description": "Overlay defines configuration for the OpenAPI Overlay (https://github.com/OAI/Overlay-Specification) to manipulate the OpenAPI specification before generation. This allows modifying the specification without needing to apply changes directly to it, making it easier to keep it up-to-date.", + "properties": { + "path": { + "description": "The path to the Overlay file", + "type": "string" + }, + "strict": { + "type": "boolean", + "description": "Strict defines whether the Overlay should be applied in a strict way, highlighting any actions that will not take any effect. This can, however, lead to more work when testing new actions in an Overlay, so can be turned off with this setting.", + "default": true + } + }, + "required": ["path"] + }, + "yaml-tags": { + "type": "boolean", + "description": "Enable the generation of YAML tags for struct fields" + }, + "client-response-bytes-function": { + "type": "boolean", + "description": "Enable the generation of a `Bytes()` method on response objects for `ClientWithResponses`" + }, + "prefer-skip-optional-pointer": { + "type": "boolean", + "description": "Allows defining at a global level whether to omit the pointer for a type to indicate that the field/type is optional. This is the same as adding `x-go-type-skip-optional-pointer` to each field (manually, or using an OpenAPI Overlay). A field can set `x-go-type-skip-optional-pointer: false` to still require the optional pointer.", + "default": false + }, + "prefer-skip-optional-pointer-with-omitzero": { + "type": "boolean", + "description": "When using `prefer-skip-optional-pointer`, generate the `omitzero` JSON tag for types that would have had an optional pointer. This is the same as adding `x-omitzero` to each field (manually, or using an OpenAPI Overlay). A field can set `x-omitzero: false` to disable the `omitzero` JSON tag.\nNOTE that this requires Go 1.24+.\nNOTE that this must be used alongside `prefer-skip-optional-pointer`, otherwise makes no difference.", + "default": false + }, + "prefer-skip-optional-pointer-on-container-types": { + "type": "boolean", + "description": "Allows disabling the generation of an 'optional pointer' for an optional field that is a container type (such as a slice or a map), which ends up requiring an additional, unnecessary, `... != nil` check. A field can set `x-go-type-skip-optional-pointer: false` to still require the optional pointer.", + "default": false + }, + "resolve-type-name-collisions": { + "type": "boolean", + "description": "When set to true, automatically renames types that collide across different OpenAPI component sections (schemas, parameters, requestBodies, responses, headers) by appending a suffix based on the component section. Also detects collisions between component types and client response wrapper types. Without this, the codegen will error on duplicate type names, requiring manual resolution via x-go-name.", + "default": false + }, + "type-mapping": { + "type": "object", + "additionalProperties": false, + "description": "TypeMapping allows customizing OpenAPI type/format to Go type mappings. User-specified mappings are merged on top of the defaults, so you only need to specify the types you want to override.", + "properties": { + "integer": { + "$ref": "#/$defs/format-mapping" + }, + "number": { + "$ref": "#/$defs/format-mapping" + }, + "boolean": { + "$ref": "#/$defs/format-mapping" + }, + "string": { + "$ref": "#/$defs/format-mapping" + } + } + } + } + }, + "import-mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "description": "ImportMapping specifies the golang package path for each external reference. A value of `-` will indicate that the current package will be used" + } + }, + "additional-imports": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "alias": { + "type": "string" + }, + "package": { + "type": "string" + } + }, + "required": ["package"] + }, + "description": "AdditionalImports defines any additional Go imports to add to the generated code" + }, + "output": { + "type": "string", + "description": "The filename to output" + } + }, + "required": [ + "package", + "output" + ], + "$defs": { + "simple-type-spec": { + "type": "object", + "additionalProperties": false, + "description": "Specifies a Go type and optional import path", + "properties": { + "type": { + "type": "string", + "description": "The Go type to use (e.g. \"int64\", \"time.Time\", \"github.com/shopspring/decimal.Decimal\")" + }, + "import": { + "type": "string", + "description": "The Go import path required for this type (e.g. \"time\", \"encoding/json\")" + } + }, + "required": [ + "type" + ] + }, + "format-mapping": { + "type": "object", + "additionalProperties": false, + "description": "Maps an OpenAPI type's formats to Go types", + "properties": { + "default": { + "$ref": "#/$defs/simple-type-spec", + "description": "The default Go type when no format is specified or the format is unrecognized" + }, + "formats": { + "type": "object", + "description": "Format-specific Go type overrides (e.g. \"int32\": {\"type\": \"int32\"}, \"double\": {\"type\": \"float64\"})", + "additionalProperties": { + "$ref": "#/$defs/simple-type-spec" + } + } + } + } + } +} diff --git a/examples/Makefile b/examples/Makefile new file mode 100644 index 0000000000..5ec0edd058 --- /dev/null +++ b/examples/Makefile @@ -0,0 +1,17 @@ +lint: + $(GOBIN)/golangci-lint run ./... + +lint-ci: + $(GOBIN)/golangci-lint run ./... --output.text.path=stdout --timeout=5m + +generate: + go generate ./... + +test: + go test -cover ./... + +tidy: + go mod tidy + +tidy-ci: + tidied -verbose diff --git a/examples/anyof-allof-oneof/anyofallofoneof.gen.go b/examples/anyof-allof-oneof/anyofallofoneof.gen.go new file mode 100644 index 0000000000..99b8616833 --- /dev/null +++ b/examples/anyof-allof-oneof/anyofallofoneof.gen.go @@ -0,0 +1,167 @@ +// Package anyofallofoneof provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package anyofallofoneof + +import ( + "encoding/json" + + "github.com/oapi-codegen/runtime" +) + +// Client defines model for Client. +type Client struct { + Name string `json:"name"` +} + +// ClientAndMaybeIdentity defines model for ClientAndMaybeIdentity. +type ClientAndMaybeIdentity struct { + union json.RawMessage +} + +// ClientOrIdentity defines model for ClientOrIdentity. +type ClientOrIdentity struct { + union json.RawMessage +} + +// ClientWithId defines model for ClientWithId. +type ClientWithId struct { + Id int `json:"id"` + Name string `json:"name"` +} + +// Identity defines model for Identity. +type Identity struct { + Issuer string `json:"issuer"` +} + +// IdentityWithDuplicateField defines model for IdentityWithDuplicateField. +type IdentityWithDuplicateField struct { + Issuer struct { + Name string `json:"name"` + } `json:"issuer"` +} + +// AsClient returns the union data inside the ClientAndMaybeIdentity as a Client +func (t ClientAndMaybeIdentity) AsClient() (Client, error) { + var body Client + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromClient overwrites any union data inside the ClientAndMaybeIdentity as the provided Client +func (t *ClientAndMaybeIdentity) FromClient(v Client) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeClient performs a merge with any union data inside the ClientAndMaybeIdentity, using the provided Client +func (t *ClientAndMaybeIdentity) MergeClient(v Client) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsIdentity returns the union data inside the ClientAndMaybeIdentity as a Identity +func (t ClientAndMaybeIdentity) AsIdentity() (Identity, error) { + var body Identity + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromIdentity overwrites any union data inside the ClientAndMaybeIdentity as the provided Identity +func (t *ClientAndMaybeIdentity) FromIdentity(v Identity) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeIdentity performs a merge with any union data inside the ClientAndMaybeIdentity, using the provided Identity +func (t *ClientAndMaybeIdentity) MergeIdentity(v Identity) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t ClientAndMaybeIdentity) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *ClientAndMaybeIdentity) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsClient returns the union data inside the ClientOrIdentity as a Client +func (t ClientOrIdentity) AsClient() (Client, error) { + var body Client + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromClient overwrites any union data inside the ClientOrIdentity as the provided Client +func (t *ClientOrIdentity) FromClient(v Client) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeClient performs a merge with any union data inside the ClientOrIdentity, using the provided Client +func (t *ClientOrIdentity) MergeClient(v Client) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsIdentity returns the union data inside the ClientOrIdentity as a Identity +func (t ClientOrIdentity) AsIdentity() (Identity, error) { + var body Identity + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromIdentity overwrites any union data inside the ClientOrIdentity as the provided Identity +func (t *ClientOrIdentity) FromIdentity(v Identity) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeIdentity performs a merge with any union data inside the ClientOrIdentity, using the provided Identity +func (t *ClientOrIdentity) MergeIdentity(v Identity) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t ClientOrIdentity) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *ClientOrIdentity) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} diff --git a/examples/anyof-allof-oneof/api.yaml b/examples/anyof-allof-oneof/api.yaml new file mode 100644 index 0000000000..61c6a494f1 --- /dev/null +++ b/examples/anyof-allof-oneof/api.yaml @@ -0,0 +1,64 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Using complex schemas + description: An example of `anyOf`, `allOf` and `oneOf` +components: + schemas: + # base types + Client: + type: object + required: + - name + properties: + name: + type: string + Identity: + type: object + required: + - issuer + properties: + issuer: + type: string + + # allOf performs a union of all types defined + ClientWithId: + allOf: + - $ref: '#/components/schemas/Client' + - properties: + id: + type: integer + required: + - id + + # allOf performs a union of all types defined, but if there's a duplicate field defined, it'll be overwritten by the last schema + # https://github.com/oapi-codegen/oapi-codegen/issues/1569 + IdentityWithDuplicateField: + allOf: + # `issuer` will be ignored + - $ref: '#/components/schemas/Identity' + # `issuer` will be ignored + - properties: + issuer: + type: integer + # `issuer` will take precedence + - properties: + issuer: + type: object + properties: + name: + type: string + required: + - name + + # anyOf results in a type that has an `AsClient`/`MergeClient`/`FromClient` and an `AsIdentity`/`MergeIdentity`/`FromIdentity` method so you can choose which of them you want to retrieve + ClientAndMaybeIdentity: + anyOf: + - $ref: '#/components/schemas/Client' + - $ref: '#/components/schemas/Identity' + + # oneOf results in a type that has an `AsClient`/`MergeClient`/`FromClient` and an `AsIdentity`/`MergeIdentity`/`FromIdentity` method so you can choose which of them you want to retrieve + ClientOrIdentity: + oneOf: + - $ref: '#/components/schemas/Client' + - $ref: '#/components/schemas/Identity' diff --git a/examples/anyof-allof-oneof/cfg.yaml b/examples/anyof-allof-oneof/cfg.yaml new file mode 100644 index 0000000000..6db7862270 --- /dev/null +++ b/examples/anyof-allof-oneof/cfg.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../configuration-schema.json +package: anyofallofoneof +output: anyofallofoneof.gen.go +generate: + models: true +output-options: + # NOTE that this is only required for the `Unreferenced` type + skip-prune: true diff --git a/examples/anyof-allof-oneof/generate.go b/examples/anyof-allof-oneof/generate.go new file mode 100644 index 0000000000..9e993e2b9f --- /dev/null +++ b/examples/anyof-allof-oneof/generate.go @@ -0,0 +1,3 @@ +package anyofallofoneof + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/authenticated-api/README.md b/examples/authenticated-api/README.md index 3687e29f90..282b1c8ef3 100644 --- a/examples/authenticated-api/README.md +++ b/examples/authenticated-api/README.md @@ -35,7 +35,7 @@ This means that all API endpoints require a JWT bearer token for access, and without any specific scopes, as denoted by `[]`. However, we want our `addThing` operation to require a special write permission, -denoted by `things:w`. This is a convention that we use in naming scopes, +denoted by `things:w`. This is a convention that we use in naming scopes, noun followed by type of access, in this case, `:w` means write. Read permission is implicit from having a valid JWT. @@ -55,8 +55,7 @@ instead of implementing too much ourselves. We've chosen to use the excellent validation, and the [kin-openapi](https://github.com/getkin/kin-openapi/tree/master/openapi3filter) request filter to help us perform validation. -First, we need to configure our [OapiRequestValidator](https://github.com/deepmap/oapi-codegen/blob/master/pkg/middleware/oapi_validate.go) -to perform authentication: +First, we need to configure our `OapiRequestValidator` to perform authentication: ```go validator := middleware.OapiRequestValidatorWithOptions(spec, &middleware.Options{ diff --git a/examples/authenticated-api/echo/api/api.gen.go b/examples/authenticated-api/echo/api/api.gen.go index 1433f2d5fd..383ab5dc25 100644 --- a/examples/authenticated-api/echo/api/api.gen.go +++ b/examples/authenticated-api/echo/api/api.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api import ( @@ -123,7 +123,7 @@ type ClientInterface interface { // ListThings request ListThings(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - // AddThing request with any body + // AddThingWithBody request with any body AddThingWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) AddThing(ctx context.Context, body AddThingJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -275,10 +275,10 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { - // ListThings request + // ListThingsWithResponse request ListThingsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListThingsResponse, error) - // AddThing request with any body + // AddThingWithBodyWithResponse request with any body AddThingWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AddThingResponse, error) AddThingWithResponse(ctx context.Context, body AddThingJSONRequestBody, reqEditors ...RequestEditorFn) (*AddThingResponse, error) @@ -425,9 +425,9 @@ type ServerInterfaceWrapper struct { func (w *ServerInterfaceWrapper) ListThings(ctx echo.Context) error { var err error - ctx.Set(BearerAuthScopes, []string{""}) + ctx.Set(BearerAuthScopes, []string{}) - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.ListThings(ctx) return err } @@ -438,7 +438,7 @@ func (w *ServerInterfaceWrapper) AddThing(ctx echo.Context) error { ctx.Set(BearerAuthScopes, []string{"things:w"}) - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.AddThing(ctx) return err } @@ -497,16 +497,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -524,7 +524,7 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } @@ -538,12 +538,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/examples/authenticated-api/echo/api/config.yaml b/examples/authenticated-api/echo/api/config.yaml index 0b176ca618..0893383bc2 100644 --- a/examples/authenticated-api/echo/api/config.yaml +++ b/examples/authenticated-api/echo/api/config.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: api generate: echo-server: true diff --git a/examples/authenticated-api/echo/api/doc.go b/examples/authenticated-api/echo/api/doc.go index 51a3a8292d..e6d8f45d97 100644 --- a/examples/authenticated-api/echo/api/doc.go +++ b/examples/authenticated-api/echo/api/doc.go @@ -1,3 +1,3 @@ package api -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml ../../api.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../../api.yaml diff --git a/examples/authenticated-api/echo/main.go b/examples/authenticated-api/echo/main.go index 0bd6ff2452..dc4b42c45a 100644 --- a/examples/authenticated-api/echo/main.go +++ b/examples/authenticated-api/echo/main.go @@ -2,17 +2,16 @@ package main import ( "flag" - "fmt" "log" + "net" - "github.com/deepmap/oapi-codegen/examples/authenticated-api/echo/api" - "github.com/deepmap/oapi-codegen/examples/authenticated-api/echo/server" "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" + "github.com/oapi-codegen/oapi-codegen/v2/examples/authenticated-api/echo/api" + "github.com/oapi-codegen/oapi-codegen/v2/examples/authenticated-api/echo/server" ) func main() { - var port = flag.Int("port", 8080, "port where to serve traffic") + port := flag.String("port", "8080", "port where to serve traffic") e := echo.New() @@ -28,7 +27,6 @@ func main() { if err != nil { log.Fatalln("error creating middleware:", err) } - e.Use(middleware.Logger()) e.Use(mw...) svr := server.NewServer() @@ -50,5 +48,5 @@ func main() { log.Println("Reader token", string(readerJWS)) log.Println("Writer token", string(writerJWS)) - e.Logger.Fatal(e.Start(fmt.Sprintf("0.0.0.0:%d", *port))) + e.Logger.Fatal(e.Start(net.JoinHostPort("0.0.0.0", *port))) } diff --git a/examples/authenticated-api/echo/server/fake_jws.go b/examples/authenticated-api/echo/server/fake_jws.go index 928b2dc048..6eb3067d86 100644 --- a/examples/authenticated-api/echo/server/fake_jws.go +++ b/examples/authenticated-api/echo/server/fake_jws.go @@ -4,16 +4,17 @@ import ( "crypto/ecdsa" "fmt" - "github.com/deepmap/oapi-codegen/pkg/ecdsafile" "github.com/lestrrat-go/jwx/jwa" "github.com/lestrrat-go/jwx/jwk" "github.com/lestrrat-go/jwx/jws" "github.com/lestrrat-go/jwx/jwt" + "github.com/oapi-codegen/oapi-codegen/v2/pkg/ecdsafile" ) // PrivateKey is an ECDSA private key which was generated with the following // command: -// openssl ecparam -name prime256v1 -genkey -noout -out ecprivatekey.pem +// +// openssl ecparam -name prime256v1 -genkey -noout -out ecprivatekey.pem // // We are using a hard coded key here in this example, but in real applications, // you would never do this. Your JWT signing key must never be in your application, @@ -74,7 +75,7 @@ func (f *FakeAuthenticator) ValidateJWS(jwsString string) (jwt.Token, error) { jwt.WithAudience(FakeAudience), jwt.WithIssuer(FakeIssuer)) } -// SignToken takes a JWT and signs it with our priviate key, returning a JWS. +// SignToken takes a JWT and signs it with our private key, returning a JWS. func (f *FakeAuthenticator) SignToken(t jwt.Token) ([]byte, error) { hdr := jws.NewHeaders() if err := hdr.Set(jws.AlgorithmKey, jwa.ES256); err != nil { diff --git a/examples/authenticated-api/echo/server/jwt_authenticator.go b/examples/authenticated-api/echo/server/jwt_authenticator.go index 4d6b334abd..c9d9a73a5a 100644 --- a/examples/authenticated-api/echo/server/jwt_authenticator.go +++ b/examples/authenticated-api/echo/server/jwt_authenticator.go @@ -9,6 +9,7 @@ import ( "github.com/getkin/kin-openapi/openapi3filter" "github.com/lestrrat-go/jwx/jwt" + middleware "github.com/oapi-codegen/echo-middleware" ) // JWSValidator is used to validate JWS payloads and return a JWT if they're @@ -17,10 +18,12 @@ type JWSValidator interface { ValidateJWS(jws string) (jwt.Token, error) } +const JWTClaimsContextKey = "jwt_claims" + var ( - ErrNoAuthHeader = errors.New("Authorization header is missing") - ErrInvalidAuthHeader = errors.New("Authorization header is malformed") - ErrClaimsInvalid = errors.New("Provided claims do not match expected scopes") + ErrNoAuthHeader = errors.New("authorization header is missing") + ErrInvalidAuthHeader = errors.New("authorization header is malformed") + ErrClaimsInvalid = errors.New("provided claims do not match expected scopes") ) // GetJWSFromRequest extracts a JWS string from an Authorization: Bearer header @@ -73,6 +76,12 @@ func Authenticate(v JWSValidator, ctx context.Context, input *openapi3filter.Aut if err != nil { return fmt.Errorf("token claims don't match: %w", err) } + + // Set the property on the echo context so the handler is able to + // access the claims data we generate in here. + eCtx := middleware.GetEchoContext(ctx) + eCtx.Set(JWTClaimsContextKey, token) + return nil } diff --git a/examples/authenticated-api/echo/server/server.go b/examples/authenticated-api/echo/server/server.go index 9c18c43302..5346b77e28 100644 --- a/examples/authenticated-api/echo/server/server.go +++ b/examples/authenticated-api/echo/server/server.go @@ -6,10 +6,10 @@ import ( "sort" "sync" - "github.com/deepmap/oapi-codegen/examples/authenticated-api/echo/api" - "github.com/deepmap/oapi-codegen/pkg/middleware" "github.com/getkin/kin-openapi/openapi3filter" "github.com/labstack/echo/v4" + middleware "github.com/oapi-codegen/echo-middleware" + "github.com/oapi-codegen/oapi-codegen/v2/examples/authenticated-api/echo/api" ) type server struct { diff --git a/examples/authenticated-api/echo/server/server_test.go b/examples/authenticated-api/echo/server/server_test.go index 21366f54bb..5891c71717 100644 --- a/examples/authenticated-api/echo/server/server_test.go +++ b/examples/authenticated-api/echo/server/server_test.go @@ -4,9 +4,9 @@ import ( "net/http" "testing" - "github.com/deepmap/oapi-codegen/examples/authenticated-api/echo/api" - "github.com/deepmap/oapi-codegen/pkg/testutil" "github.com/labstack/echo/v4" + "github.com/oapi-codegen/oapi-codegen/v2/examples/authenticated-api/echo/api" + "github.com/oapi-codegen/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -34,21 +34,21 @@ func TestAPI(t *testing.T) { t.Logf("writer jwt: %s", string(writerJWT)) // ListPets should return 403 forbidden without credentials - response := testutil.NewRequest().Get("/things").Go(t, e) + response := testutil.NewRequest().Get("/things").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusForbidden, response.Code()) // Using the writer JWT should allow us to insert a thing. response = testutil.NewRequest().Post("/things"). WithJWSAuth(string(writerJWT)). WithAcceptJson(). - WithJsonBody(api.Thing{Name: "Thing 1"}).Go(t, e) + WithJsonBody(api.Thing{Name: "Thing 1"}).GoWithHTTPHandler(t, e) require.Equal(t, http.StatusCreated, response.Code()) // Using the reader JWT should forbid inserting a thing. response = testutil.NewRequest().Post("/things"). WithJWSAuth(string(readerJWT)). WithAcceptJson(). - WithJsonBody(api.Thing{Name: "Thing 2"}).Go(t, e) + WithJsonBody(api.Thing{Name: "Thing 2"}).GoWithHTTPHandler(t, e) require.Equal(t, http.StatusForbidden, response.Code()) // Both JWT's should allow reading the list of things. @@ -56,7 +56,7 @@ func TestAPI(t *testing.T) { for _, jwt := range jwts { response := testutil.NewRequest().Get("/things"). WithJWSAuth(jwt). - WithAcceptJson().Go(t, e) + WithAcceptJson().GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, response.Code()) } } diff --git a/examples/authenticated-api/stdhttp/Makefile b/examples/authenticated-api/stdhttp/Makefile new file mode 100644 index 0000000000..5ec0edd058 --- /dev/null +++ b/examples/authenticated-api/stdhttp/Makefile @@ -0,0 +1,17 @@ +lint: + $(GOBIN)/golangci-lint run ./... + +lint-ci: + $(GOBIN)/golangci-lint run ./... --output.text.path=stdout --timeout=5m + +generate: + go generate ./... + +test: + go test -cover ./... + +tidy: + go mod tidy + +tidy-ci: + tidied -verbose diff --git a/examples/authenticated-api/stdhttp/api/api.gen.go b/examples/authenticated-api/stdhttp/api/api.gen.go new file mode 100644 index 0000000000..91ced48c3d --- /dev/null +++ b/examples/authenticated-api/stdhttp/api/api.gen.go @@ -0,0 +1,681 @@ +//go:build go1.22 + +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" +) + +const ( + BearerAuthScopes = "BearerAuth.Scopes" +) + +// Error defines model for Error. +type Error struct { + // Code Error code + Code int32 `json:"code"` + + // Message Error message + Message string `json:"message"` +} + +// Thing defines model for Thing. +type Thing struct { + Name string `json:"name"` +} + +// ThingWithID defines model for ThingWithID. +type ThingWithID struct { + Id int64 `json:"id"` + Name string `json:"name"` +} + +// AddThingJSONRequestBody defines body for AddThing for application/json ContentType. +type AddThingJSONRequestBody = Thing + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // ListThings request + ListThings(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // AddThingWithBody request with any body + AddThingWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + AddThing(ctx context.Context, body AddThingJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) ListThings(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListThingsRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) AddThingWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewAddThingRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) AddThing(ctx context.Context, body AddThingJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewAddThingRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewListThingsRequest generates requests for ListThings +func NewListThingsRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/things") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewAddThingRequest calls the generic AddThing builder with application/json body +func NewAddThingRequest(server string, body AddThingJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewAddThingRequestWithBody(server, "application/json", bodyReader) +} + +// NewAddThingRequestWithBody generates requests for AddThing with any type of body +func NewAddThingRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/things") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // ListThingsWithResponse request + ListThingsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListThingsResponse, error) + + // AddThingWithBodyWithResponse request with any body + AddThingWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AddThingResponse, error) + + AddThingWithResponse(ctx context.Context, body AddThingJSONRequestBody, reqEditors ...RequestEditorFn) (*AddThingResponse, error) +} + +type ListThingsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]ThingWithID +} + +// Status returns HTTPResponse.Status +func (r ListThingsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListThingsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type AddThingResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *[]ThingWithID +} + +// Status returns HTTPResponse.Status +func (r AddThingResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r AddThingResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ListThingsWithResponse request returning *ListThingsResponse +func (c *ClientWithResponses) ListThingsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListThingsResponse, error) { + rsp, err := c.ListThings(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseListThingsResponse(rsp) +} + +// AddThingWithBodyWithResponse request with arbitrary body returning *AddThingResponse +func (c *ClientWithResponses) AddThingWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AddThingResponse, error) { + rsp, err := c.AddThingWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseAddThingResponse(rsp) +} + +func (c *ClientWithResponses) AddThingWithResponse(ctx context.Context, body AddThingJSONRequestBody, reqEditors ...RequestEditorFn) (*AddThingResponse, error) { + rsp, err := c.AddThing(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseAddThingResponse(rsp) +} + +// ParseListThingsResponse parses an HTTP response from a ListThingsWithResponse call +func ParseListThingsResponse(rsp *http.Response) (*ListThingsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListThingsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []ThingWithID + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseAddThingResponse parses an HTTP response from a AddThingWithResponse call +func ParseAddThingResponse(rsp *http.Response) (*AddThingResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &AddThingResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest []ThingWithID + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /things) + ListThings(w http.ResponseWriter, r *http.Request) + + // (POST /things) + AddThing(w http.ResponseWriter, r *http.Request) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// ListThings operation middleware +func (siw *ServerInterfaceWrapper) ListThings(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListThings(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// AddThing operation middleware +func (siw *ServerInterfaceWrapper) AddThing(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{"things:w"}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.AddThing(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{}) +} + +// ServeMux is an abstraction of http.ServeMux. +type ServeMux interface { + HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) + ServeHTTP(w http.ResponseWriter, r *http.Request) +} + +type StdHTTPServerOptions struct { + BaseURL string + BaseRouter ServeMux + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, m ServeMux) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{ + BaseRouter: m, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, m ServeMux, baseURL string) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{ + BaseURL: baseURL, + BaseRouter: m, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.Handler { + m := options.BaseRouter + + if m == nil { + m = http.NewServeMux() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + m.HandleFunc("GET "+options.BaseURL+"/things", wrapper.ListThings) + m.HandleFunc("POST "+options.BaseURL+"/things", wrapper.AddThing) + + return m +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/8RUwW7bOBD9lcHsAnsRbCdZ7EE3B8kCDgq0aA3kEAcII44ttjLJDEdxjUD/XpCUHDVO", + "0/bUiy2Swzfz3rzhE1Zu650lKwHLJwxVTVuVPi+ZHccPz84Ti6G0XTlN8V9TqNh4Mc5imYMhnRW4drxV", + "giUaK2enWKDsPeUlbYixK3BLIajND4GG48PVIGzsBruuQKaH1jBpLG+wTziE33YFLusYeFS2VduU7W28", + "FHVAuTZSLy7iLdU079dY3jzh30xrLPGv6bNu0160aU7dFS9zGx1/x6r89+8rqryoxWi87W7jbqCqZSP7", + "TzFPhjwnxcTzVuq4uk+r/4cEV9dLLHIrY4J8+pywFvHYRWBj1+64BXML9FVtfUMw/7CAXW2qGtpAATIS", + "iPtCFkLlPAVQVsPV9RJUrKVAMdLEJLE0smIqJaQTzmXGxAIfiUNOdTKZTWbRD86TVd5giWdpq0CvpE5U", + "pxJlTZ8bkuNyP5K0bAMoaEwQcGvIFyZwTpVqA8V1ALLaO2MFtKNg/xFwj8RsdDymld007l41MEhdgBHo", + "uxGhI8O148Syp2Wcnawspto5LRcaS3xngixzxbGfwTsbcs9OZ7M8QFbIJiLK+6aHmn4Okc0wgck2Qtt0", + "8aee643aHVqsmNU+9/h7sV6KlGO8C68IO9c6Uk+BIC7qdCTxcixtOGgaUnDWdGUHUSFbMllmpO1dBit3", + "d9lTYCw41slo4Inj4IBa2R0bodckn2udRy8PEAU5d3r/W1r/wlgfizl/1sbYQCwTGMwY6ec90n3UzkgN", + "ysLiAseDLtxSd+SUkz/ulOWYwXLEAFprHlqKPMaPU3odx8/SDQ59ze/Ym7Ex4lsAAAD//9UBNUSMBgAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/examples/authenticated-api/stdhttp/api/config.yaml b/examples/authenticated-api/stdhttp/api/config.yaml new file mode 100644 index 0000000000..9cbde4b66f --- /dev/null +++ b/examples/authenticated-api/stdhttp/api/config.yaml @@ -0,0 +1,10 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +generate: + std-http-server: true + client: true + models: true + embedded-spec: true +output: api.gen.go +output-options: + skip-prune: true diff --git a/examples/authenticated-api/stdhttp/api/doc.go b/examples/authenticated-api/stdhttp/api/doc.go new file mode 100644 index 0000000000..e6d8f45d97 --- /dev/null +++ b/examples/authenticated-api/stdhttp/api/doc.go @@ -0,0 +1,3 @@ +package api + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../../api.yaml diff --git a/examples/authenticated-api/stdhttp/go.mod b/examples/authenticated-api/stdhttp/go.mod new file mode 100644 index 0000000000..64d200dc01 --- /dev/null +++ b/examples/authenticated-api/stdhttp/go.mod @@ -0,0 +1,48 @@ +module github.com/oapi-codegen/oapi-codegen/v2/examples/authenticated-api/stdhttp + +go 1.24.3 + +replace github.com/oapi-codegen/oapi-codegen/v2 => ../../../ + +require ( + github.com/getkin/kin-openapi v0.133.0 + github.com/lestrrat-go/jwx v1.2.31 + github.com/oapi-codegen/nethttp-middleware v1.1.2 + github.com/oapi-codegen/oapi-codegen/v2 v2.0.0-00010101000000-000000000000 + github.com/oapi-codegen/testutil v1.1.0 + github.com/stretchr/testify v1.11.1 +) + +require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/goccy/go-json v0.10.3 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect + github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect + github.com/mailru/easyjson v0.9.1 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/speakeasy-api/jsonpath v0.6.0 // indirect + github.com/speakeasy-api/openapi-overlay v0.10.3 // indirect + github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/tools v0.42.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/examples/authenticated-api/stdhttp/go.sum b/examples/authenticated-api/stdhttp/go.sum new file mode 100644 index 0000000000..372e7efe62 --- /dev/null +++ b/examples/authenticated-api/stdhttp/go.sum @@ -0,0 +1,201 @@ +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= +github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx v1.2.31 h1:/OM9oNl/fzyldpv5HKZ9m7bTywa7COUfg8gujd9nJ54= +github.com/lestrrat-go/jwx v1.2.31/go.mod h1:eQJKoRwWcLg4PfD5CFA5gIZGxhPgoPYq9pZISdxLf0c= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oapi-codegen/nethttp-middleware v1.1.2 h1:TQwEU3WM6ifc7ObBEtiJgbRPaCe513tvJpiMJjypVPA= +github.com/oapi-codegen/nethttp-middleware v1.1.2/go.mod h1:5qzjxMSiI8HjLljiOEjvs4RdrWyMPKnExeFS2kr8om4= +github.com/oapi-codegen/testutil v1.1.0 h1:EufqpNg43acR3qzr3ObhXmWg3Sl2kwtRnUN5GYY4d5g= +github.com/oapi-codegen/testutil v1.1.0/go.mod h1:ttCaYbHvJtHuiyeBF0tPIX+4uhEPTeizXKx28okijLw= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8= +github.com/speakeasy-api/jsonpath v0.6.0/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw= +github.com/speakeasy-api/openapi-overlay v0.10.3 h1:70een4vwHyslIp796vM+ox6VISClhtXsCjrQNhxwvWs= +github.com/speakeasy-api/openapi-overlay v0.10.3/go.mod h1:RJjV0jbUHqXLS0/Mxv5XE7LAnJHqHw+01RDdpoGqiyY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= +github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/authenticated-api/stdhttp/main.go b/examples/authenticated-api/stdhttp/main.go new file mode 100644 index 0000000000..73d330aac4 --- /dev/null +++ b/examples/authenticated-api/stdhttp/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "flag" + "log" + "net/http" + + "github.com/oapi-codegen/oapi-codegen/v2/examples/authenticated-api/stdhttp/api" + "github.com/oapi-codegen/oapi-codegen/v2/examples/authenticated-api/stdhttp/server" +) + +func main() { + port := flag.String("port", "8080", "port where to serve traffic") + + r := http.NewServeMux() + + // Create a fake authenticator. This allows us to issue tokens, and also + // implements a validator to check their validity. + fa, err := server.NewFakeAuthenticator() + if err != nil { + log.Fatalln("error creating authenticator:", err) + } + + // Create middleware for validating tokens. + mw, err := server.CreateMiddleware(fa) + if err != nil { + log.Fatalln("error creating middleware:", err) + } + + svr := server.NewServer() + + h := api.HandlerFromMux(svr, r) + // wrap the existing handler with our global middleware + h = mw(h) + + // We're going to print some useful things for interacting with this server. + // This token allows access to any API's with no specific claims. + readerJWS, err := fa.CreateJWSWithClaims([]string{}) + if err != nil { + log.Fatalln("error creating reader JWS:", err) + } + // This token allows access to API's with no scopes, and with the "things:w" claim. + writerJWS, err := fa.CreateJWSWithClaims([]string{"things:w"}) + if err != nil { + log.Fatalln("error creating writer JWS:", err) + } + + log.Println("Reader token", string(readerJWS)) + log.Println("Writer token", string(writerJWS)) + + s := &http.Server{ + Handler: h, + Addr: "0.0.0.0:" + *port, + } + + log.Fatal(s.ListenAndServe()) +} diff --git a/examples/authenticated-api/stdhttp/server/fake_jws.go b/examples/authenticated-api/stdhttp/server/fake_jws.go new file mode 100644 index 0000000000..6eb3067d86 --- /dev/null +++ b/examples/authenticated-api/stdhttp/server/fake_jws.go @@ -0,0 +1,110 @@ +package server + +import ( + "crypto/ecdsa" + "fmt" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwk" + "github.com/lestrrat-go/jwx/jws" + "github.com/lestrrat-go/jwx/jwt" + "github.com/oapi-codegen/oapi-codegen/v2/pkg/ecdsafile" +) + +// PrivateKey is an ECDSA private key which was generated with the following +// command: +// +// openssl ecparam -name prime256v1 -genkey -noout -out ecprivatekey.pem +// +// We are using a hard coded key here in this example, but in real applications, +// you would never do this. Your JWT signing key must never be in your application, +// only the public key. +const PrivateKey = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIN2dALnjdcZaIZg4QuA6Dw+kxiSW502kJfmBN3priIhPoAoGCCqGSM49 +AwEHoUQDQgAE4pPyvrB9ghqkT1Llk0A42lixkugFd/TBdOp6wf69O9Nndnp4+HcR +s9SlG/8hjB2Hz42v4p3haKWv3uS1C6ahCQ== +-----END EC PRIVATE KEY-----` + +const KeyID = `fake-key-id` +const FakeIssuer = "fake-issuer" +const FakeAudience = "example-users" +const PermissionsClaim = "perm" + +type FakeAuthenticator struct { + PrivateKey *ecdsa.PrivateKey + KeySet jwk.Set +} + +var _ JWSValidator = (*FakeAuthenticator)(nil) + +// NewFakeAuthenticator creates an authenticator example which uses a hard coded +// ECDSA key to validate JWT's that it has signed itself. +func NewFakeAuthenticator() (*FakeAuthenticator, error) { + privKey, err := ecdsafile.LoadEcdsaPrivateKey([]byte(PrivateKey)) + if err != nil { + return nil, fmt.Errorf("loading PEM private key: %w", err) + } + + set := jwk.NewSet() + pubKey := jwk.NewECDSAPublicKey() + + err = pubKey.FromRaw(&privKey.PublicKey) + if err != nil { + return nil, fmt.Errorf("parsing jwk key: %w", err) + } + + err = pubKey.Set(jwk.AlgorithmKey, jwa.ES256) + if err != nil { + return nil, fmt.Errorf("setting key algorithm: %w", err) + } + + err = pubKey.Set(jwk.KeyIDKey, KeyID) + if err != nil { + return nil, fmt.Errorf("setting key ID: %w", err) + } + + set.Add(pubKey) + + return &FakeAuthenticator{PrivateKey: privKey, KeySet: set}, nil +} + +// ValidateJWS ensures that the critical JWT claims needed to ensure that we +// trust the JWT are present and with the correct values. +func (f *FakeAuthenticator) ValidateJWS(jwsString string) (jwt.Token, error) { + return jwt.Parse([]byte(jwsString), jwt.WithKeySet(f.KeySet), + jwt.WithAudience(FakeAudience), jwt.WithIssuer(FakeIssuer)) +} + +// SignToken takes a JWT and signs it with our private key, returning a JWS. +func (f *FakeAuthenticator) SignToken(t jwt.Token) ([]byte, error) { + hdr := jws.NewHeaders() + if err := hdr.Set(jws.AlgorithmKey, jwa.ES256); err != nil { + return nil, fmt.Errorf("setting algorithm: %w", err) + } + if err := hdr.Set(jws.TypeKey, "JWT"); err != nil { + return nil, fmt.Errorf("setting type: %w", err) + } + if err := hdr.Set(jws.KeyIDKey, KeyID); err != nil { + return nil, fmt.Errorf("setting Key ID: %w", err) + } + return jwt.Sign(t, jwa.ES256, f.PrivateKey, jwt.WithHeaders(hdr)) +} + +// CreateJWSWithClaims is a helper function to create JWT's with the specified +// claims. +func (f *FakeAuthenticator) CreateJWSWithClaims(claims []string) ([]byte, error) { + t := jwt.New() + err := t.Set(jwt.IssuerKey, FakeIssuer) + if err != nil { + return nil, fmt.Errorf("setting issuer: %w", err) + } + err = t.Set(jwt.AudienceKey, FakeAudience) + if err != nil { + return nil, fmt.Errorf("setting audience: %w", err) + } + err = t.Set(PermissionsClaim, claims) + if err != nil { + return nil, fmt.Errorf("setting permissions: %w", err) + } + return f.SignToken(t) +} diff --git a/examples/authenticated-api/stdhttp/server/jwt_authenticator.go b/examples/authenticated-api/stdhttp/server/jwt_authenticator.go new file mode 100644 index 0000000000..b29a67302c --- /dev/null +++ b/examples/authenticated-api/stdhttp/server/jwt_authenticator.go @@ -0,0 +1,135 @@ +package server + +import ( + "context" + "errors" + "fmt" + "net/http" + "strings" + + "github.com/getkin/kin-openapi/openapi3filter" + "github.com/lestrrat-go/jwx/jwt" +) + +// JWSValidator is used to validate JWS payloads and return a JWT if they're +// valid +type JWSValidator interface { + ValidateJWS(jws string) (jwt.Token, error) +} + +const JWTClaimsContextKey = "jwt_claims" + +var ( + ErrNoAuthHeader = errors.New("authorization header is missing") + ErrInvalidAuthHeader = errors.New("authorization header is malformed") + ErrClaimsInvalid = errors.New("provided claims do not match expected scopes") +) + +// GetJWSFromRequest extracts a JWS string from an Authorization: Bearer header +func GetJWSFromRequest(req *http.Request) (string, error) { + authHdr := req.Header.Get("Authorization") + // Check for the Authorization header. + if authHdr == "" { + return "", ErrNoAuthHeader + } + // We expect a header value of the form "Bearer ", with 1 space after + // Bearer, per spec. + prefix := "Bearer " + if !strings.HasPrefix(authHdr, prefix) { + return "", ErrInvalidAuthHeader + } + return strings.TrimPrefix(authHdr, prefix), nil +} + +func NewAuthenticator(v JWSValidator) openapi3filter.AuthenticationFunc { + return func(ctx context.Context, input *openapi3filter.AuthenticationInput) error { + return Authenticate(v, ctx, input) + } +} + +// Authenticate uses the specified validator to ensure a JWT is valid, then makes +// sure that the claims provided by the JWT match the scopes as required in the API. +func Authenticate(v JWSValidator, ctx context.Context, input *openapi3filter.AuthenticationInput) error { + // Our security scheme is named BearerAuth, ensure this is the case + if input.SecuritySchemeName != "BearerAuth" { + return fmt.Errorf("security scheme %s != 'BearerAuth'", input.SecuritySchemeName) + } + + // Now, we need to get the JWS from the request, to match the request expectations + // against request contents. + jws, err := GetJWSFromRequest(input.RequestValidationInput.Request) + if err != nil { + return fmt.Errorf("getting jws: %w", err) + } + + // if the JWS is valid, we have a JWT, which will contain a bunch of claims. + token, err := v.ValidateJWS(jws) + if err != nil { + return fmt.Errorf("validating JWS: %w", err) + } + + // We've got a valid token now, and we can look into its claims to see whether + // they match. Every single scope must be present in the claims. + err = CheckTokenClaims(input.Scopes, token) + + if err != nil { + return fmt.Errorf("token claims don't match: %w", err) + } + + // Set the property on the echo context so the handler is able to + // access the claims data we generate in here. + // TODO + // ctx.Set(JWTClaimsContextKey, token) + + return nil +} + +// GetClaimsFromToken returns a list of claims from the token. We store these +// as a list under the "perms" claim, short for permissions, to keep the token +// shorter. +func GetClaimsFromToken(t jwt.Token) ([]string, error) { + rawPerms, found := t.Get(PermissionsClaim) + if !found { + // If the perms aren't found, it means that the token has none, but it has + // passed signature validation by now, so it's a valid token, so we return + // the empty list. + return make([]string, 0), nil + } + + // rawPerms will be an untyped JSON list, so we need to convert it to + // a string list. + rawList, ok := rawPerms.([]interface{}) + if !ok { + return nil, fmt.Errorf("'%s' claim is unexpected type'", PermissionsClaim) + } + + claims := make([]string, len(rawList)) + + for i, rawClaim := range rawList { + var ok bool + claims[i], ok = rawClaim.(string) + if !ok { + return nil, fmt.Errorf("%s[%d] is not a string", PermissionsClaim, i) + } + } + return claims, nil +} + +func CheckTokenClaims(expectedClaims []string, t jwt.Token) error { + claims, err := GetClaimsFromToken(t) + if err != nil { + return fmt.Errorf("getting claims from token: %w", err) + } + // Put the claims into a map, for quick access. + claimsMap := make(map[string]bool, len(claims)) + for _, c := range claims { + claimsMap[c] = true + } + + for _, e := range expectedClaims { + if !claimsMap[e] { + return ErrClaimsInvalid + } + } + return nil +} diff --git a/examples/authenticated-api/stdhttp/server/server.go b/examples/authenticated-api/stdhttp/server/server.go new file mode 100644 index 0000000000..ddcabaa737 --- /dev/null +++ b/examples/authenticated-api/stdhttp/server/server.go @@ -0,0 +1,122 @@ +package server + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "sort" + "sync" + + "github.com/getkin/kin-openapi/openapi3filter" + middleware "github.com/oapi-codegen/nethttp-middleware" + "github.com/oapi-codegen/oapi-codegen/v2/examples/authenticated-api/stdhttp/api" +) + +type server struct { + sync.RWMutex + lastID int64 + things map[int64]api.Thing +} + +func NewServer() *server { + return &server{ + lastID: 0, + things: make(map[int64]api.Thing), + } +} + +func CreateMiddleware(v JWSValidator) (func(next http.Handler) http.Handler, error) { + spec, err := api.GetSwagger() + if err != nil { + return nil, fmt.Errorf("loading spec: %w", err) + } + + validator := middleware.OapiRequestValidatorWithOptions(spec, + &middleware.Options{ + Options: openapi3filter.Options{ + AuthenticationFunc: NewAuthenticator(v), + }, + }) + + return validator, nil +} + +// Ensure that we implement the server interface +var _ api.ServerInterface = (*server)(nil) + +func (s *server) ListThings(w http.ResponseWriter, r *http.Request) { + // This handler will only be called when a valid JWT is presented for + // access. + s.RLock() + + thingKeys := make([]int64, 0, len(s.things)) + for key := range s.things { + thingKeys = append(thingKeys, key) + } + sort.Sort(int64s(thingKeys)) + + things := make([]api.ThingWithID, 0, len(s.things)) + + for _, key := range thingKeys { + thing := s.things[key] + things = append(things, api.ThingWithID{ + Id: key, + Name: thing.Name, + }) + } + + s.RUnlock() + + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(things) +} + +type int64s []int64 + +func (in int64s) Len() int { + return len(in) +} + +func (in int64s) Less(i, j int) bool { + return in[i] < in[j] +} + +func (in int64s) Swap(i, j int) { + in[i], in[j] = in[j], in[i] +} + +var _ sort.Interface = (int64s)(nil) + +func (s *server) AddThing(w http.ResponseWriter, r *http.Request) { + // This handler will only be called when the JWT is valid and the JWT contains + // the scopes required. + bodyBytes, err := io.ReadAll(r.Body) + defer func() { _ = r.Body.Close() }() + if err != nil { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte("could not bind request body")) + return + } + + var thing api.Thing + err = json.Unmarshal(bodyBytes, &thing) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte("could not bind request body")) + return + } + + s.Lock() + defer s.Unlock() + + s.things[s.lastID] = thing + thingWithId := api.ThingWithID{ + Name: thing.Name, + Id: s.lastID, + } + s.lastID++ + + w.WriteHeader(http.StatusCreated) + _ = json.NewEncoder(w).Encode(thingWithId) +} diff --git a/examples/authenticated-api/stdhttp/server/server_test.go b/examples/authenticated-api/stdhttp/server/server_test.go new file mode 100644 index 0000000000..cfada646da --- /dev/null +++ b/examples/authenticated-api/stdhttp/server/server_test.go @@ -0,0 +1,63 @@ +package server + +import ( + "net/http" + "testing" + + "github.com/oapi-codegen/oapi-codegen/v2/examples/authenticated-api/stdhttp/api" + "github.com/oapi-codegen/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAPI(t *testing.T) { + r := http.NewServeMux() + s := NewServer() + + fa, err := NewFakeAuthenticator() + require.NoError(t, err) + + mw, err := CreateMiddleware(fa) + require.NoError(t, err) + + h := api.HandlerFromMux(s, r) + // wrap the existing handler with our global middleware + h = mw(h) + + // Let's create a JWT with no scopes, which allows access to listing things. + readerJWT, err := fa.CreateJWSWithClaims([]string{}) + require.NoError(t, err) + t.Logf("reader jwt: %s", string(readerJWT)) + + // Now, create a JWT with write permission. + writerJWT, err := fa.CreateJWSWithClaims([]string{"things:w"}) + require.NoError(t, err) + t.Logf("writer jwt: %s", string(writerJWT)) + + // ListPets should return 401 Unauthorized without credentials + response := testutil.NewRequest().Get("/things").GoWithHTTPHandler(t, h) + assert.Equal(t, http.StatusUnauthorized, response.Code()) + + // Using the writer JWT should allow us to insert a thing. + response = testutil.NewRequest().Post("/things"). + WithJWSAuth(string(writerJWT)). + WithAcceptJson(). + WithJsonBody(api.Thing{Name: "Thing 1"}).GoWithHTTPHandler(t, h) + require.Equal(t, http.StatusCreated, response.Code()) + + // Using the reader JWT should forbid inserting a thing. + response = testutil.NewRequest().Post("/things"). + WithJWSAuth(string(readerJWT)). + WithAcceptJson(). + WithJsonBody(api.Thing{Name: "Thing 2"}).GoWithHTTPHandler(t, h) + require.Equal(t, http.StatusUnauthorized, response.Code()) + + // Both JWT's should allow reading the list of things. + jwts := []string{string(readerJWT), string(writerJWT)} + for _, jwt := range jwts { + response := testutil.NewRequest().Get("/things"). + WithJWSAuth(jwt). + WithAcceptJson().GoWithHTTPHandler(t, h) + assert.Equal(t, http.StatusOK, response.Code()) + } +} diff --git a/examples/authenticated-api/stdhttp/tools/tools.go b/examples/authenticated-api/stdhttp/tools/tools.go new file mode 100644 index 0000000000..8615cb4c57 --- /dev/null +++ b/examples/authenticated-api/stdhttp/tools/tools.go @@ -0,0 +1,8 @@ +//go:build tools +// +build tools + +package tools + +import ( + _ "github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen" +) diff --git a/examples/client/api.yaml b/examples/client/api.yaml new file mode 100644 index 0000000000..5945b63dd7 --- /dev/null +++ b/examples/client/api.yaml @@ -0,0 +1,47 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Generate models +paths: + /client: + get: + operationId: getClient + responses: + 200: + content: + application/json: + schema: + $ref: "#/components/schemas/ClientType" + put: + operationId: updateClient + responses: + 400: + content: + application/json: + schema: + type: object + properties: + code: + type: string + required: + - code +components: + schemas: + ClientType: + type: object + required: + - name + properties: + name: + type: string + # NOTE that this is not generated by default because it's not referenced. If you want it, you need to use the following YAML configuration: + # + # output-options: + # skip-prune: true + Unreferenced: + type: object + required: + - id + properties: + id: + type: int diff --git a/examples/client/cfg.yaml b/examples/client/cfg.yaml new file mode 100644 index 0000000000..d2103b0561 --- /dev/null +++ b/examples/client/cfg.yaml @@ -0,0 +1,6 @@ +# yaml-language-server: $schema=../../configuration-schema.json +package: client +output: client.gen.go +generate: + models: true + client: true diff --git a/examples/client/client.gen.go b/examples/client/client.gen.go new file mode 100644 index 0000000000..8ef6b78564 --- /dev/null +++ b/examples/client/client.gen.go @@ -0,0 +1,345 @@ +// Package client provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package client + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" +) + +// ClientType defines model for ClientType. +type ClientType struct { + Name string `json:"name"` +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetClient request + GetClient(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UpdateClient request + UpdateClient(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) GetClient(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetClientRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateClient(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateClientRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetClientRequest generates requests for GetClient +func NewGetClientRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/client") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewUpdateClientRequest generates requests for UpdateClient +func NewUpdateClientRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/client") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PUT", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetClientWithResponse request + GetClientWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetClientResponse, error) + + // UpdateClientWithResponse request + UpdateClientWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UpdateClientResponse, error) +} + +type GetClientResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *ClientType +} + +// Status returns HTTPResponse.Status +func (r GetClientResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetClientResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UpdateClientResponse struct { + Body []byte + HTTPResponse *http.Response + JSON400 *struct { + Code string `json:"code"` + } +} + +// Status returns HTTPResponse.Status +func (r UpdateClientResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateClientResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// GetClientWithResponse request returning *GetClientResponse +func (c *ClientWithResponses) GetClientWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetClientResponse, error) { + rsp, err := c.GetClient(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetClientResponse(rsp) +} + +// UpdateClientWithResponse request returning *UpdateClientResponse +func (c *ClientWithResponses) UpdateClientWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UpdateClientResponse, error) { + rsp, err := c.UpdateClient(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateClientResponse(rsp) +} + +// ParseGetClientResponse parses an HTTP response from a GetClientWithResponse call +func ParseGetClientResponse(rsp *http.Response) (*GetClientResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetClientResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest ClientType + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseUpdateClientResponse parses an HTTP response from a UpdateClientWithResponse call +func ParseUpdateClientResponse(rsp *http.Response) (*UpdateClientResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateClientResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest struct { + Code string `json:"code"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + } + + return response, nil +} diff --git a/examples/client/generate.go b/examples/client/generate.go new file mode 100644 index 0000000000..e9c9a42f86 --- /dev/null +++ b/examples/client/generate.go @@ -0,0 +1,3 @@ +package client + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/clienttypenameclash/api.yaml b/examples/clienttypenameclash/api.yaml new file mode 100644 index 0000000000..681f9d0b7e --- /dev/null +++ b/examples/clienttypenameclash/api.yaml @@ -0,0 +1,34 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: "Show that generated client boilerplate can clash if schemas are well named i.e. `*Request` and `*Response`" +paths: + /client: + put: + operationId: updateClient + requestBodies: + application/json: + schema: + $ref: '#/components/schemas/UpdateClientRequest' + responses: + 400: + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateClientResponse' +components: + schemas: + UpdateClientRequest: + type: object + properties: + code: + type: string + required: + - code + UpdateClientResponse: + type: object + required: + - name + properties: + name: + type: string diff --git a/examples/clienttypenameclash/cfg.yaml b/examples/clienttypenameclash/cfg.yaml new file mode 100644 index 0000000000..0723317b4b --- /dev/null +++ b/examples/clienttypenameclash/cfg.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../configuration-schema.json +package: client +output: client.gen.go +generate: + models: true + client: true +output-options: + response-type-suffix: Resp diff --git a/examples/clienttypenameclash/client.gen.go b/examples/clienttypenameclash/client.gen.go new file mode 100644 index 0000000000..219a2a0d4d --- /dev/null +++ b/examples/clienttypenameclash/client.gen.go @@ -0,0 +1,239 @@ +// Package client provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package client + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" +) + +// UpdateClientResponse defines model for UpdateClientResponse. +type UpdateClientResponse struct { + Name string `json:"name"` +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // UpdateClient request + UpdateClient(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) UpdateClient(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateClientRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewUpdateClientRequest generates requests for UpdateClient +func NewUpdateClientRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/client") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PUT", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // UpdateClientWithResponse request + UpdateClientWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UpdateClientResp, error) +} + +type UpdateClientResp struct { + Body []byte + HTTPResponse *http.Response + JSON400 *UpdateClientResponse +} + +// Status returns HTTPResponse.Status +func (r UpdateClientResp) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateClientResp) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// UpdateClientWithResponse request returning *UpdateClientResp +func (c *ClientWithResponses) UpdateClientWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UpdateClientResp, error) { + rsp, err := c.UpdateClient(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateClientResp(rsp) +} + +// ParseUpdateClientResp parses an HTTP response from a UpdateClientWithResponse call +func ParseUpdateClientResp(rsp *http.Response) (*UpdateClientResp, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateClientResp{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest UpdateClientResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + } + + return response, nil +} diff --git a/examples/clienttypenameclash/generate.go b/examples/clienttypenameclash/generate.go new file mode 100644 index 0000000000..e9c9a42f86 --- /dev/null +++ b/examples/clienttypenameclash/generate.go @@ -0,0 +1,3 @@ +package client + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/custom-client-type/cfg.yaml b/examples/custom-client-type/cfg.yaml index 6f52fb3db7..7dcbbbc264 100644 --- a/examples/custom-client-type/cfg.yaml +++ b/examples/custom-client-type/cfg.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../configuration-schema.json package: customclienttype output: custom-client-type.gen.go generate: diff --git a/examples/custom-client-type/custom-client-type.gen.go b/examples/custom-client-type/custom-client-type.gen.go index 34c34ea99e..6dd2ef0195 100644 --- a/examples/custom-client-type/custom-client-type.gen.go +++ b/examples/custom-client-type/custom-client-type.gen.go @@ -1,6 +1,6 @@ // Package customclienttype provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package customclienttype import ( @@ -177,7 +177,7 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { - // GetClient request + // GetClientWithResponse request GetClientWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetClientResponse, error) } diff --git a/examples/custom-client-type/doc.go b/examples/custom-client-type/doc.go index b718ac2cb3..89172d392e 100644 --- a/examples/custom-client-type/doc.go +++ b/examples/custom-client-type/doc.go @@ -1,6 +1,6 @@ package customclienttype // This is an example of how to add a prefix to the name of the generated Client struct -// See https://github.com/deepmap/oapi-codegen/issues/785 for why this might be necessary +// See https://github.com/oapi-codegen/oapi-codegen/issues/785 for why this might be necessary -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen -config cfg.yaml api.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/extensions/xdeprecatedreason/api.yaml b/examples/extensions/xdeprecatedreason/api.yaml new file mode 100644 index 0000000000..c5056dd207 --- /dev/null +++ b/examples/extensions/xdeprecatedreason/api.yaml @@ -0,0 +1,32 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-deprecated-reason +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + type: string + id: + type: number + ClientWithExtension: + type: object + required: + - name + properties: + name: + type: string + deprecated: true + x-deprecated-reason: Don't use because reasons + id: + type: number + # NOTE that this doesn't generate, as no `deprecated: true` is set + x-deprecated-reason: NOTE you shouldn't see this, as you've not deprecated this field + deprecated_without_reason: + type: string + deprecated: true + # no `x-deprecated-reason` is set diff --git a/examples/extensions/xdeprecatedreason/cfg.yaml b/examples/extensions/xdeprecatedreason/cfg.yaml new file mode 100644 index 0000000000..05ae8c244b --- /dev/null +++ b/examples/extensions/xdeprecatedreason/cfg.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: xdeprecatedreason +output: gen.go +generate: + models: true +output-options: + # to make sure that all types are generated, even if they're unreferenced + skip-prune: true diff --git a/examples/extensions/xdeprecatedreason/gen.go b/examples/extensions/xdeprecatedreason/gen.go new file mode 100644 index 0000000000..3855905f92 --- /dev/null +++ b/examples/extensions/xdeprecatedreason/gen.go @@ -0,0 +1,19 @@ +// Package xdeprecatedreason provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package xdeprecatedreason + +// Client defines model for Client. +type Client struct { + Id *float32 `json:"id,omitempty"` + Name string `json:"name"` +} + +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension struct { + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + DeprecatedWithoutReason *string `json:"deprecated_without_reason,omitempty"` + Id *float32 `json:"id,omitempty"` + // Deprecated: Don't use because reasons + Name string `json:"name"` +} diff --git a/examples/extensions/xdeprecatedreason/generate.go b/examples/extensions/xdeprecatedreason/generate.go new file mode 100644 index 0000000000..97a610ff37 --- /dev/null +++ b/examples/extensions/xdeprecatedreason/generate.go @@ -0,0 +1,3 @@ +package xdeprecatedreason + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/extensions/xenumnames/api.yaml b/examples/extensions/xenumnames/api.yaml new file mode 100644 index 0000000000..b9c271582d --- /dev/null +++ b/examples/extensions/xenumnames/api.yaml @@ -0,0 +1,27 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-enumNames and x-enum-varnames +components: + schemas: + ClientType: + type: string + enum: + - ACT + - EXP + ClientTypeWithNamesExtension: + type: string + enum: + - ACT + - EXP + x-enumNames: + - Active + - Expired + ClientTypeWithVarNamesExtension: + type: string + enum: + - ACT + - EXP + x-enum-varnames: + - Active + - Expired diff --git a/examples/extensions/xenumnames/cfg.yaml b/examples/extensions/xenumnames/cfg.yaml new file mode 100644 index 0000000000..694e667328 --- /dev/null +++ b/examples/extensions/xenumnames/cfg.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: xenumnames +output: gen.go +generate: + models: true +output-options: + # to make sure that all types are generated, even if they're unreferenced + skip-prune: true diff --git a/examples/extensions/xenumnames/gen.go b/examples/extensions/xenumnames/gen.go new file mode 100644 index 0000000000..73ebfa656c --- /dev/null +++ b/examples/extensions/xenumnames/gen.go @@ -0,0 +1,31 @@ +// Package xenumnames provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package xenumnames + +// Defines values for ClientType. +const ( + ACT ClientType = "ACT" + EXP ClientType = "EXP" +) + +// Defines values for ClientTypeWithNamesExtension. +const ( + ClientTypeWithNamesExtensionActive ClientTypeWithNamesExtension = "ACT" + ClientTypeWithNamesExtensionExpired ClientTypeWithNamesExtension = "EXP" +) + +// Defines values for ClientTypeWithVarNamesExtension. +const ( + ClientTypeWithVarNamesExtensionActive ClientTypeWithVarNamesExtension = "ACT" + ClientTypeWithVarNamesExtensionExpired ClientTypeWithVarNamesExtension = "EXP" +) + +// ClientType defines model for ClientType. +type ClientType string + +// ClientTypeWithNamesExtension defines model for ClientTypeWithNamesExtension. +type ClientTypeWithNamesExtension string + +// ClientTypeWithVarNamesExtension defines model for ClientTypeWithVarNamesExtension. +type ClientTypeWithVarNamesExtension string diff --git a/examples/extensions/xenumnames/generate.go b/examples/extensions/xenumnames/generate.go new file mode 100644 index 0000000000..599d6f3f91 --- /dev/null +++ b/examples/extensions/xenumnames/generate.go @@ -0,0 +1,3 @@ +package xenumnames + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/extensions/xgojsonignore/api.yaml b/examples/extensions/xgojsonignore/api.yaml new file mode 100644 index 0000000000..31af087f3e --- /dev/null +++ b/examples/extensions/xgojsonignore/api.yaml @@ -0,0 +1,37 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-go-json-ignore +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + type: string + complexField: + type: object + properties: + name: + type: string + accountName: + type: string + # ... + ClientWithExtension: + type: object + required: + - name + properties: + name: + type: string + complexField: + type: object + properties: + name: + type: string + accountName: + type: string + # ... + x-go-json-ignore: true diff --git a/examples/extensions/xgojsonignore/cfg.yaml b/examples/extensions/xgojsonignore/cfg.yaml new file mode 100644 index 0000000000..2e8300b09e --- /dev/null +++ b/examples/extensions/xgojsonignore/cfg.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: xgojsonignore +output: gen.go +generate: + models: true +output-options: + # to make sure that all types are generated, even if they're unreferenced + skip-prune: true diff --git a/examples/extensions/xgojsonignore/gen.go b/examples/extensions/xgojsonignore/gen.go new file mode 100644 index 0000000000..17c21373da --- /dev/null +++ b/examples/extensions/xgojsonignore/gen.go @@ -0,0 +1,22 @@ +// Package xgojsonignore provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package xgojsonignore + +// Client defines model for Client. +type Client struct { + ComplexField *struct { + AccountName *string `json:"accountName,omitempty"` + Name *string `json:"name,omitempty"` + } `json:"complexField,omitempty"` + Name string `json:"name"` +} + +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension struct { + ComplexField *struct { + AccountName *string `json:"accountName,omitempty"` + Name *string `json:"name,omitempty"` + } `json:"-"` + Name string `json:"name"` +} diff --git a/examples/extensions/xgojsonignore/generate.go b/examples/extensions/xgojsonignore/generate.go new file mode 100644 index 0000000000..0d8488e438 --- /dev/null +++ b/examples/extensions/xgojsonignore/generate.go @@ -0,0 +1,3 @@ +package xgojsonignore + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/extensions/xgoname/api.yaml b/examples/extensions/xgoname/api.yaml new file mode 100644 index 0000000000..579412c918 --- /dev/null +++ b/examples/extensions/xgoname/api.yaml @@ -0,0 +1,27 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-go-name +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + type: string + id: + type: number + ClientWithExtension: + type: object + x-go-name: ClientRenamedByExtension + required: + - name + properties: + name: + type: string + id: + type: number + # maybe we want to be intentionally verbose here? + x-go-name: AccountIdentifier diff --git a/examples/extensions/xgoname/cfg.yaml b/examples/extensions/xgoname/cfg.yaml new file mode 100644 index 0000000000..7dc65386df --- /dev/null +++ b/examples/extensions/xgoname/cfg.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: xgoname +output: gen.go +generate: + models: true +output-options: + # to make sure that all types are generated, even if they're unreferenced + skip-prune: true diff --git a/examples/extensions/xgoname/gen.go b/examples/extensions/xgoname/gen.go new file mode 100644 index 0000000000..c7831379a1 --- /dev/null +++ b/examples/extensions/xgoname/gen.go @@ -0,0 +1,16 @@ +// Package xgoname provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package xgoname + +// Client defines model for Client. +type Client struct { + Id *float32 `json:"id,omitempty"` + Name string `json:"name"` +} + +// ClientRenamedByExtension defines model for ClientWithExtension. +type ClientRenamedByExtension struct { + AccountIdentifier *float32 `json:"id,omitempty"` + Name string `json:"name"` +} diff --git a/examples/extensions/xgoname/generate.go b/examples/extensions/xgoname/generate.go new file mode 100644 index 0000000000..5462114199 --- /dev/null +++ b/examples/extensions/xgoname/generate.go @@ -0,0 +1,3 @@ +package xgoname + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/extensions/xgotype/api.yaml b/examples/extensions/xgotype/api.yaml new file mode 100644 index 0000000000..5efdb02722 --- /dev/null +++ b/examples/extensions/xgotype/api.yaml @@ -0,0 +1,36 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-go-type +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + type: string + id: + type: number + ClientWithExtension: + type: object + required: + - name + properties: + name: + type: string + # this is a bit of a contrived example, as you could instead use + # `format: uuid` but it explains how you'd do this when there may be + # a clash, for instance if you already had a `uuid` package that was + # being imported, or ... + x-go-type: googleuuid.UUID + x-go-type-import: + path: github.com/google/uuid + name: googleuuid + id: + type: number + # ... this is also a bit of a contrived example, as you could use + # `type: integer` but in the case that you know better than what + # oapi-codegen is generating, like so: + x-go-type: int64 diff --git a/examples/extensions/xgotype/cfg.yaml b/examples/extensions/xgotype/cfg.yaml new file mode 100644 index 0000000000..afc39fcd7a --- /dev/null +++ b/examples/extensions/xgotype/cfg.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: xgotype +output: gen.go +generate: + models: true +output-options: + # to make sure that all types are generated, even if they're unreferenced + skip-prune: true diff --git a/examples/extensions/xgotype/gen.go b/examples/extensions/xgotype/gen.go new file mode 100644 index 0000000000..376c1fdbff --- /dev/null +++ b/examples/extensions/xgotype/gen.go @@ -0,0 +1,20 @@ +// Package xgotype provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package xgotype + +import ( + googleuuid "github.com/google/uuid" +) + +// Client defines model for Client. +type Client struct { + Id *float32 `json:"id,omitempty"` + Name string `json:"name"` +} + +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension struct { + Id *int64 `json:"id,omitempty"` + Name googleuuid.UUID `json:"name"` +} diff --git a/examples/extensions/xgotype/generate.go b/examples/extensions/xgotype/generate.go new file mode 100644 index 0000000000..fc28c53de8 --- /dev/null +++ b/examples/extensions/xgotype/generate.go @@ -0,0 +1,3 @@ +package xgotype + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/extensions/xgotypename/api.yaml b/examples/extensions/xgotypename/api.yaml new file mode 100644 index 0000000000..17b83cb0f0 --- /dev/null +++ b/examples/extensions/xgotypename/api.yaml @@ -0,0 +1,27 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-go-type-name +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + type: string + id: + type: number + ClientWithExtension: + type: object + x-go-type-name: ClientRenamedByExtension + required: + - name + properties: + name: + type: string + id: + type: number + # NOTE attempting a `x-go-type-name` here is a no-op, as we're not producing a _type_ only a _field_ + x-go-type-name: ThisWillNotBeUsed diff --git a/examples/extensions/xgotypename/cfg.yaml b/examples/extensions/xgotypename/cfg.yaml new file mode 100644 index 0000000000..3a903b657f --- /dev/null +++ b/examples/extensions/xgotypename/cfg.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: xgotypename +output: gen.go +generate: + models: true +output-options: + # to make sure that all types are generated, even if they're unreferenced + skip-prune: true diff --git a/examples/extensions/xgotypename/gen.go b/examples/extensions/xgotypename/gen.go new file mode 100644 index 0000000000..cc0c9c2bf5 --- /dev/null +++ b/examples/extensions/xgotypename/gen.go @@ -0,0 +1,19 @@ +// Package xgotypename provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package xgotypename + +// Client defines model for Client. +type Client struct { + Id *float32 `json:"id,omitempty"` + Name string `json:"name"` +} + +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension = ClientRenamedByExtension + +// ClientRenamedByExtension defines model for . +type ClientRenamedByExtension struct { + Id *float32 `json:"id,omitempty"` + Name string `json:"name"` +} diff --git a/examples/extensions/xgotypename/generate.go b/examples/extensions/xgotypename/generate.go new file mode 100644 index 0000000000..787661180b --- /dev/null +++ b/examples/extensions/xgotypename/generate.go @@ -0,0 +1,3 @@ +package xgotypename + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/extensions/xgotypeskipoptionalpointer/api.yaml b/examples/extensions/xgotypeskipoptionalpointer/api.yaml new file mode 100644 index 0000000000..5dac5532b4 --- /dev/null +++ b/examples/extensions/xgotypeskipoptionalpointer/api.yaml @@ -0,0 +1,25 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-go-type-skip-optional-pointer +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + type: string + id: + type: number + ClientWithExtension: + type: object + required: + - name + properties: + name: + type: string + id: + type: number + x-go-type-skip-optional-pointer: true diff --git a/examples/extensions/xgotypeskipoptionalpointer/cfg.yaml b/examples/extensions/xgotypeskipoptionalpointer/cfg.yaml new file mode 100644 index 0000000000..bb01706cf9 --- /dev/null +++ b/examples/extensions/xgotypeskipoptionalpointer/cfg.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: xgotypeskipoptionalpointer +output: gen.go +generate: + models: true +output-options: + # to make sure that all types are generated, even if they're unreferenced + skip-prune: true diff --git a/examples/extensions/xgotypeskipoptionalpointer/gen.go b/examples/extensions/xgotypeskipoptionalpointer/gen.go new file mode 100644 index 0000000000..ddb1df8436 --- /dev/null +++ b/examples/extensions/xgotypeskipoptionalpointer/gen.go @@ -0,0 +1,16 @@ +// Package xgotypeskipoptionalpointer provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package xgotypeskipoptionalpointer + +// Client defines model for Client. +type Client struct { + Id *float32 `json:"id,omitempty"` + Name string `json:"name"` +} + +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension struct { + Id float32 `json:"id,omitempty"` + Name string `json:"name"` +} diff --git a/examples/extensions/xgotypeskipoptionalpointer/generate.go b/examples/extensions/xgotypeskipoptionalpointer/generate.go new file mode 100644 index 0000000000..582ec97fdd --- /dev/null +++ b/examples/extensions/xgotypeskipoptionalpointer/generate.go @@ -0,0 +1,3 @@ +package xgotypeskipoptionalpointer + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/extensions/xoapicodegenextratags/api.yaml b/examples/extensions/xoapicodegenextratags/api.yaml new file mode 100644 index 0000000000..34771334a4 --- /dev/null +++ b/examples/extensions/xoapicodegenextratags/api.yaml @@ -0,0 +1,30 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-oapi-codegen-extra-tags +components: + schemas: + Client: + type: object + required: + - name + - id + properties: + name: + type: string + id: + type: number + ClientWithExtension: + type: object + required: + - name + - id + properties: + name: + type: string + id: + type: number + x-oapi-codegen-extra-tags: + validate: "required,min=1,max=256" + safe-to-log: "true" + gorm: primarykey diff --git a/examples/extensions/xoapicodegenextratags/cfg.yaml b/examples/extensions/xoapicodegenextratags/cfg.yaml new file mode 100644 index 0000000000..582d1932a9 --- /dev/null +++ b/examples/extensions/xoapicodegenextratags/cfg.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: xoapicodegenextratags +output: gen.go +generate: + models: true +output-options: + # to make sure that all types are generated, even if they're unreferenced + skip-prune: true diff --git a/examples/extensions/xoapicodegenextratags/gen.go b/examples/extensions/xoapicodegenextratags/gen.go new file mode 100644 index 0000000000..25f950e3d4 --- /dev/null +++ b/examples/extensions/xoapicodegenextratags/gen.go @@ -0,0 +1,16 @@ +// Package xoapicodegenextratags provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package xoapicodegenextratags + +// Client defines model for Client. +type Client struct { + Id float32 `json:"id"` + Name string `json:"name"` +} + +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension struct { + Id float32 `gorm:"primarykey" json:"id" safe-to-log:"true" validate:"required,min=1,max=256"` + Name string `json:"name"` +} diff --git a/examples/extensions/xoapicodegenextratags/generate.go b/examples/extensions/xoapicodegenextratags/generate.go new file mode 100644 index 0000000000..6906128b00 --- /dev/null +++ b/examples/extensions/xoapicodegenextratags/generate.go @@ -0,0 +1,3 @@ +package xoapicodegenextratags + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/extensions/xoapicodegenonlyhonourgoname/api.yaml b/examples/extensions/xoapicodegenonlyhonourgoname/api.yaml new file mode 100644 index 0000000000..7a1fc9e3ce --- /dev/null +++ b/examples/extensions/xoapicodegenonlyhonourgoname/api.yaml @@ -0,0 +1,18 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-oapi-codegen-only-honour-go-name +components: + schemas: + TypeWithUnexportedField: + description: A struct will be output where one of the fields is not exported + properties: + name: + type: string + id: + type: string + # NOTE that there is an explicit usage of a lowercase character + x-go-name: accountIdentifier + x-oapi-codegen-extra-tags: + json: "-" + x-oapi-codegen-only-honour-go-name: true diff --git a/examples/extensions/xoapicodegenonlyhonourgoname/cfg.yaml b/examples/extensions/xoapicodegenonlyhonourgoname/cfg.yaml new file mode 100644 index 0000000000..222b37e2ef --- /dev/null +++ b/examples/extensions/xoapicodegenonlyhonourgoname/cfg.yaml @@ -0,0 +1,10 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: xoapicodegenonlyhonourgoname +output: gen.go +generate: + models: true +output-options: + # to make sure that all types are generated, even if they're unreferenced + skip-prune: true +compatibility: + allow-unexported-struct-field-names: true diff --git a/examples/extensions/xoapicodegenonlyhonourgoname/gen.go b/examples/extensions/xoapicodegenonlyhonourgoname/gen.go new file mode 100644 index 0000000000..71e3cca4b5 --- /dev/null +++ b/examples/extensions/xoapicodegenonlyhonourgoname/gen.go @@ -0,0 +1,10 @@ +// Package xoapicodegenonlyhonourgoname provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package xoapicodegenonlyhonourgoname + +// TypeWithUnexportedField A struct will be output where one of the fields is not exported +type TypeWithUnexportedField struct { + accountIdentifier *string `json:"-"` + Name *string `json:"name,omitempty"` +} diff --git a/examples/extensions/xoapicodegenonlyhonourgoname/gen_test.go b/examples/extensions/xoapicodegenonlyhonourgoname/gen_test.go new file mode 100644 index 0000000000..3e616bf99c --- /dev/null +++ b/examples/extensions/xoapicodegenonlyhonourgoname/gen_test.go @@ -0,0 +1,18 @@ +package xoapicodegenonlyhonourgoname + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestTypeWithUnexportedField(t *testing.T) { + var v TypeWithUnexportedField + + err := json.Unmarshal([]byte(`{"id": "some-id"}`), &v) + require.NoError(t, err) + + // this field will never be unmarshaled + require.Nil(t, v.accountIdentifier) +} diff --git a/examples/extensions/xoapicodegenonlyhonourgoname/generate.go b/examples/extensions/xoapicodegenonlyhonourgoname/generate.go new file mode 100644 index 0000000000..938d489126 --- /dev/null +++ b/examples/extensions/xoapicodegenonlyhonourgoname/generate.go @@ -0,0 +1,3 @@ +package xoapicodegenonlyhonourgoname + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/extensions/xomitempty/api.yaml b/examples/extensions/xomitempty/api.yaml new file mode 100644 index 0000000000..d01919942d --- /dev/null +++ b/examples/extensions/xomitempty/api.yaml @@ -0,0 +1,26 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-omitempty +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + type: string + id: + type: number + ClientWithExtension: + type: object + required: + - name + properties: + name: + type: string + # for some reason, you may want this behaviour, even though it's a required field + x-omitempty: true + id: + type: number diff --git a/examples/extensions/xomitempty/cfg.yaml b/examples/extensions/xomitempty/cfg.yaml new file mode 100644 index 0000000000..fe8c985087 --- /dev/null +++ b/examples/extensions/xomitempty/cfg.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: xomitempty +output: gen.go +generate: + models: true +output-options: + # to make sure that all types are generated, even if they're unreferenced + skip-prune: true diff --git a/examples/extensions/xomitempty/gen.go b/examples/extensions/xomitempty/gen.go new file mode 100644 index 0000000000..a4a06f97e6 --- /dev/null +++ b/examples/extensions/xomitempty/gen.go @@ -0,0 +1,16 @@ +// Package xomitempty provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package xomitempty + +// Client defines model for Client. +type Client struct { + Id *float32 `json:"id,omitempty"` + Name string `json:"name"` +} + +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension struct { + Id *float32 `json:"id,omitempty"` + Name string `json:"name,omitempty"` +} diff --git a/examples/extensions/xomitempty/generate.go b/examples/extensions/xomitempty/generate.go new file mode 100644 index 0000000000..c6c48c4fed --- /dev/null +++ b/examples/extensions/xomitempty/generate.go @@ -0,0 +1,3 @@ +package xomitempty + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/extensions/xomitzero/api.yaml b/examples/extensions/xomitzero/api.yaml new file mode 100644 index 0000000000..63b9b24c2b --- /dev/null +++ b/examples/extensions/xomitzero/api.yaml @@ -0,0 +1,53 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-omitempty +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + type: string + id: + type: number + ClientWithExtension: + type: object + required: + - name + properties: + name: + type: string + id: + type: number + x-omitzero: true + ContainerTypeWithRequired: + type: object + properties: + has_is_zero: + $ref: "#/components/schemas/FieldWithCustomIsZeroMethod" + required: + - has_is_zero + ContainerTypeWithOptional: + type: object + properties: + has_is_zero: + $ref: "#/components/schemas/FieldWithCustomIsZeroMethod" + FieldWithCustomIsZeroMethod: + type: object + properties: + id: + type: string + value: + type: number + x-omitzero: true + FieldWithOmitZeroOnRequiredField: + type: object + properties: + id: + type: string + x-omitzero: true + required: + - id diff --git a/examples/extensions/xomitzero/cfg.yaml b/examples/extensions/xomitzero/cfg.yaml new file mode 100644 index 0000000000..bcec1dd3c6 --- /dev/null +++ b/examples/extensions/xomitzero/cfg.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: xomitzero +output: types.gen.go +generate: + models: true +output-options: + # to make sure that all types are generated, even if they're unreferenced + skip-prune: true diff --git a/examples/extensions/xomitzero/generate.go b/examples/extensions/xomitzero/generate.go new file mode 100644 index 0000000000..70b6c27164 --- /dev/null +++ b/examples/extensions/xomitzero/generate.go @@ -0,0 +1,3 @@ +package xomitzero + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/extensions/xomitzero/tools/tools.go b/examples/extensions/xomitzero/tools/tools.go new file mode 100644 index 0000000000..8615cb4c57 --- /dev/null +++ b/examples/extensions/xomitzero/tools/tools.go @@ -0,0 +1,8 @@ +//go:build tools +// +build tools + +package tools + +import ( + _ "github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen" +) diff --git a/examples/extensions/xomitzero/types.gen.go b/examples/extensions/xomitzero/types.gen.go new file mode 100644 index 0000000000..fe5e927d95 --- /dev/null +++ b/examples/extensions/xomitzero/types.gen.go @@ -0,0 +1,37 @@ +// Package xomitzero provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package xomitzero + +// Client defines model for Client. +type Client struct { + Id *float32 `json:"id,omitempty"` + Name string `json:"name"` +} + +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension struct { + Id *float32 `json:"id,omitempty,omitzero"` + Name string `json:"name"` +} + +// ContainerTypeWithOptional defines model for ContainerTypeWithOptional. +type ContainerTypeWithOptional struct { + HasIsZero *FieldWithCustomIsZeroMethod `json:"has_is_zero,omitempty,omitzero"` +} + +// ContainerTypeWithRequired defines model for ContainerTypeWithRequired. +type ContainerTypeWithRequired struct { + HasIsZero FieldWithCustomIsZeroMethod `json:"has_is_zero,omitzero"` +} + +// FieldWithCustomIsZeroMethod defines model for FieldWithCustomIsZeroMethod. +type FieldWithCustomIsZeroMethod struct { + Id *string `json:"id,omitempty"` + Value *float32 `json:"value,omitempty"` +} + +// FieldWithOmitZeroOnRequiredField defines model for FieldWithOmitZeroOnRequiredField. +type FieldWithOmitZeroOnRequiredField struct { + Id string `json:"id,omitzero"` +} diff --git a/examples/extensions/xomitzero/types.go b/examples/extensions/xomitzero/types.go new file mode 100644 index 0000000000..fed32fb8f0 --- /dev/null +++ b/examples/extensions/xomitzero/types.go @@ -0,0 +1,20 @@ +package xomitzero + +type isZero interface { + IsZero() bool +} + +var _ isZero = (*FieldWithCustomIsZeroMethod)(nil) + +func (z FieldWithCustomIsZeroMethod) IsZero() bool { + // NOTE that this is intentionally not a "normal" use of the function, but is a way to indicate that the `IsZero` used here can be anything arbitrary + if z.Id == nil { + return false + } + + if *z.Id != "this is a zero value, for some weird reason!" { + return false + } + + return true +} diff --git a/examples/extensions/xomitzero/types_test.go b/examples/extensions/xomitzero/types_test.go new file mode 100644 index 0000000000..496d345d3c --- /dev/null +++ b/examples/extensions/xomitzero/types_test.go @@ -0,0 +1,236 @@ +package xomitzero + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestClient_WithOmitEmpty(t *testing.T) { + t.Run("with a `string`, without `omitempty`", func(t *testing.T) { + t.Run("zero value Name does not get omitted", func(t *testing.T) { + client := Client{ + Name: "", + } + + b, err := json.Marshal(client) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "name")) + }) + + t.Run("value Name does not get omitted", func(t *testing.T) { + client := Client{ + Name: "some value", + } + + b, err := json.Marshal(client) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "name")) + }) + }) + + t.Run("with a `*float32` with `omitempty`", func(t *testing.T) { + var zeroValue float32 + + t.Run("nil pointer ID gets omitted", func(t *testing.T) { + client := Client{ + Id: nil, + } + + b, err := json.Marshal(client) + require.NoError(t, err) + + assert.False(t, jsonContainsKey(b, "id")) + }) + + t.Run("pointer to zero value ID does not get omitted", func(t *testing.T) { + client := Client{ + Id: &zeroValue, + } + + b, err := json.Marshal(client) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "id")) + }) + + t.Run("pointer to value ID does not get omitted", func(t *testing.T) { + client := Client{ + Id: &zeroValue, + } + + b, err := json.Marshal(client) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "id")) + }) + }) +} + +func TestClientWithExtension_WithOmitZero(t *testing.T) { + t.Run("with a `string`, without `omitzero`", func(t *testing.T) { + t.Run("zero value Name does not get omitted", func(t *testing.T) { + client := ClientWithExtension{ + Name: "", + } + + b, err := json.Marshal(client) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "name")) + }) + + t.Run("value Name does not get omitted", func(t *testing.T) { + client := ClientWithExtension{ + Name: "some value", + } + + b, err := json.Marshal(client) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "name")) + }) + }) + + t.Run("with a `*float32` with `omitzero`", func(t *testing.T) { + var zeroValue float32 + + t.Run("nil pointer ID gets omitted", func(t *testing.T) { + client := ClientWithExtension{ + Id: nil, + } + + b, err := json.Marshal(client) + require.NoError(t, err) + + assert.False(t, jsonContainsKey(b, "id")) + }) + + t.Run("pointer to zero value ID does not get omitted", func(t *testing.T) { + client := ClientWithExtension{ + Id: &zeroValue, + } + + b, err := json.Marshal(client) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "id")) + }) + + t.Run("pointer to value ID does not get omitted", func(t *testing.T) { + client := ClientWithExtension{ + Id: &zeroValue, + } + + b, err := json.Marshal(client) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "id")) + }) + }) +} + +func TestContainerTypeWithRequired(t *testing.T) { + t.Run("zero value on HasIsZero does not get omitted", func(t *testing.T) { + container := ContainerTypeWithRequired{ + HasIsZero: FieldWithCustomIsZeroMethod{}, + } + + b, err := json.Marshal(container) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "has_is_zero")) + }) + + t.Run("value defined as zero value by IsZero on HasIsZero gets omitted", func(t *testing.T) { + magicIDValue := "this is a zero value, for some weird reason!" + + container := ContainerTypeWithRequired{ + HasIsZero: FieldWithCustomIsZeroMethod{ + Id: &magicIDValue, + }, + } + + b, err := json.Marshal(container) + require.NoError(t, err) + + assert.False(t, jsonContainsKey(b, "has_is_zero")) + }) +} + +func TestContainerTypeWithOptional(t *testing.T) { + t.Run("zero value (nil pointer) on HasIsZero gets omitted", func(t *testing.T) { + container := ContainerTypeWithOptional{ + HasIsZero: nil, + } + + b, err := json.Marshal(container) + require.NoError(t, err) + + assert.False(t, jsonContainsKey(b, "has_is_zero")) + }) + + t.Run("value (pointer to zero value of FieldWithCustomIsZeroMethod) on HasIsZero does not get omitted", func(t *testing.T) { + container := ContainerTypeWithOptional{ + HasIsZero: &FieldWithCustomIsZeroMethod{}, + } + + b, err := json.Marshal(container) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "has_is_zero")) + }) + + t.Run("value defined as zero value by IsZero on HasIsZero gets omitted", func(t *testing.T) { + magicIDValue := "this is a zero value, for some weird reason!" + + container := ContainerTypeWithOptional{ + HasIsZero: &FieldWithCustomIsZeroMethod{ + Id: &magicIDValue, + }, + } + + b, err := json.Marshal(container) + require.NoError(t, err) + + assert.False(t, jsonContainsKey(b, "has_is_zero")) + }) +} + +func TestFieldWithOmitZeroOnRequiredField(t *testing.T) { + t.Run("zero value (empty string) on Id gets omitted", func(t *testing.T) { + field := FieldWithOmitZeroOnRequiredField{ + Id: "", + } + + b, err := json.Marshal(field) + require.NoError(t, err) + + assert.False(t, jsonContainsKey(b, "id")) + }) + + t.Run("value for Id not get omitted", func(t *testing.T) { + field := FieldWithOmitZeroOnRequiredField{ + Id: "value", + } + + b, err := json.Marshal(field) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "id")) + }) +} + +// jsonContainsKey checks if the given JSON object contains the specified key at the top level. +func jsonContainsKey(b []byte, key string) bool { + var m map[string]any + if err := json.Unmarshal(b, &m); err != nil { + return false + } + _, ok := m[key] + return ok +} diff --git a/examples/extensions/xorder/api.yaml b/examples/extensions/xorder/api.yaml new file mode 100644 index 0000000000..5171a0def1 --- /dev/null +++ b/examples/extensions/xorder/api.yaml @@ -0,0 +1,26 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: x-order +components: + schemas: + Client: + type: object + required: + - name + properties: + a_name: + type: string + id: + type: number + ClientWithExtension: + type: object + required: + - name + properties: + a_name: + type: string + x-order: 2 + id: + type: number + x-order: 1 diff --git a/examples/extensions/xorder/cfg.yaml b/examples/extensions/xorder/cfg.yaml new file mode 100644 index 0000000000..0d3851f478 --- /dev/null +++ b/examples/extensions/xorder/cfg.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: xorder +output: gen.go +generate: + models: true +output-options: + # to make sure that all types are generated, even if they're unreferenced + skip-prune: true diff --git a/examples/extensions/xorder/gen.go b/examples/extensions/xorder/gen.go new file mode 100644 index 0000000000..bb5841d564 --- /dev/null +++ b/examples/extensions/xorder/gen.go @@ -0,0 +1,16 @@ +// Package xorder provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package xorder + +// Client defines model for Client. +type Client struct { + AName *string `json:"a_name,omitempty"` + Id *float32 `json:"id,omitempty"` +} + +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension struct { + Id *float32 `json:"id,omitempty"` + AName *string `json:"a_name,omitempty"` +} diff --git a/examples/extensions/xorder/generate.go b/examples/extensions/xorder/generate.go new file mode 100644 index 0000000000..9b9d46563e --- /dev/null +++ b/examples/extensions/xorder/generate.go @@ -0,0 +1,3 @@ +package xorder + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/generate/serverurls/api.yaml b/examples/generate/serverurls/api.yaml new file mode 100644 index 0000000000..48695165ba --- /dev/null +++ b/examples/generate/serverurls/api.yaml @@ -0,0 +1,47 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Server URLs can be optionally generated +servers: +# adapted from https://spec.openapis.org/oas/v3.0.3#server-object +- url: https://development.gigantic-server.com/v1 + description: Development server +- url: https://staging.gigantic-server.com/v1 + description: Staging server +- url: https://api.gigantic-server.com/v1 + description: Production server +# adapted from https://spec.openapis.org/oas/v3.0.3#server-object +- url: https://{username}.gigantic-server.com:{port}/{basePath} + description: The production API server + variables: + username: + # note! no enum here means it is an open value + default: demo + description: this value is assigned by the service provider, in this example `gigantic-server.com` + port: + enum: + - '8443' + - '443' + default: '8443' + basePath: + # open meaning there is the opportunity to use special base paths as assigned by the provider, default is `v2` + default: v2 + # an example of a type that's defined, but doesn't have a default + noDefault: {} + # # TODO this conflict will cause broken generated code https://github.com/oapi-codegen/oapi-codegen/issues/2003 + # conflicting: + # enum: + # - 'default' + # - '443' + # default: 'default' +# clash with the previous definition of `Development server` to trigger a new name +- url: http://localhost:80 + description: Development server +# clash with the previous definition of `Development server` to trigger a new name (again) +- url: http://localhost:80 + description: Development server +# make sure that the lowercase `description` gets converted to an uppercase +- url: http://localhost:80 + description: some lowercase name +# there may be URLs on their own, without a `description` +- url: http://localhost:443 diff --git a/examples/generate/serverurls/cfg.yaml b/examples/generate/serverurls/cfg.yaml new file mode 100644 index 0000000000..8815cb6a68 --- /dev/null +++ b/examples/generate/serverurls/cfg.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: serverurls +output: gen.go +generate: + server-urls: true +output-options: + # to make sure that all types are generated, even if they're unreferenced + skip-prune: true diff --git a/examples/generate/serverurls/gen.go b/examples/generate/serverurls/gen.go new file mode 100644 index 0000000000..b32073b2cd --- /dev/null +++ b/examples/generate/serverurls/gen.go @@ -0,0 +1,67 @@ +// Package serverurls provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package serverurls + +import ( + "fmt" + "strings" +) + +// ServerUrlDevelopmentServer defines the Server URL for Development server + +// ServerUrlDevelopmentServer1 defines the Server URL for Development server + +// ServerUrlDevelopmentServer2 defines the Server URL for Development server + +// ServerUrlHttplocalhost443 defines the Server URL for http://localhost:443 + +// ServerUrlProductionServer defines the Server URL for Production server + +// ServerUrlSomeLowercaseName defines the Server URL for some lowercase name + +// ServerUrlStagingServer defines the Server URL for Staging server + +// ServerUrlTheProductionAPIServerBasePathVariable is the `basePath` variable for ServerUrlTheProductionAPIServer +type ServerUrlTheProductionAPIServerBasePathVariable string + +// ServerUrlTheProductionAPIServerBasePathVariableDefault is the default value for the `basePath` variable for ServerUrlTheProductionAPIServer +const ServerUrlTheProductionAPIServerBasePathVariableDefault = "v2" + +// ServerUrlTheProductionAPIServerNoDefaultVariable is the `noDefault` variable for ServerUrlTheProductionAPIServer +type ServerUrlTheProductionAPIServerNoDefaultVariable string + +// ServerUrlTheProductionAPIServerPortVariable is the `port` variable for ServerUrlTheProductionAPIServer +type ServerUrlTheProductionAPIServerPortVariable string + +// ServerUrlTheProductionAPIServerPortVariable8443 is one of the accepted values for the `port` variable for ServerUrlTheProductionAPIServer +const ServerUrlTheProductionAPIServerPortVariable8443 ServerUrlTheProductionAPIServerPortVariable = "8443" + +// ServerUrlTheProductionAPIServerPortVariable443 is one of the accepted values for the `port` variable for ServerUrlTheProductionAPIServer +const ServerUrlTheProductionAPIServerPortVariable443 ServerUrlTheProductionAPIServerPortVariable = "443" + +// ServerUrlTheProductionAPIServerPortVariableDefault is the default choice, for the accepted values for the `port` variable for ServerUrlTheProductionAPIServer +const ServerUrlTheProductionAPIServerPortVariableDefault ServerUrlTheProductionAPIServerPortVariable = ServerUrlTheProductionAPIServerPortVariable8443 + +// ServerUrlTheProductionAPIServerUsernameVariable is the `username` variable for ServerUrlTheProductionAPIServer +type ServerUrlTheProductionAPIServerUsernameVariable string + +// ServerUrlTheProductionAPIServerUsernameVariableDefault is the default value for the `username` variable for ServerUrlTheProductionAPIServer +const ServerUrlTheProductionAPIServerUsernameVariableDefault = "demo" + +// NewServerUrlTheProductionAPIServer constructs the Server URL for The production API server, with the provided variables. +func NewServerUrlTheProductionAPIServer(basePath ServerUrlTheProductionAPIServerBasePathVariable, noDefault ServerUrlTheProductionAPIServerNoDefaultVariable, port ServerUrlTheProductionAPIServerPortVariable, username ServerUrlTheProductionAPIServerUsernameVariable) (string, error) { + u := "https://{username}.gigantic-server.com:{port}/{basePath}" + + u = strings.ReplaceAll(u, "{basePath}", string(basePath)) + u = strings.ReplaceAll(u, "{noDefault}", string(noDefault)) + // TODO in the future, this will validate that the value is part of the ServerUrlTheProductionAPIServerPortVariable enum + u = strings.ReplaceAll(u, "{port}", string(port)) + u = strings.ReplaceAll(u, "{username}", string(username)) + + if strings.Contains(u, "{") || strings.Contains(u, "}") { + return "", fmt.Errorf("after mapping variables, there were still `{` or `}` characters in the string: %#v", u) + } + + return u, nil +} diff --git a/examples/generate/serverurls/gen_test.go b/examples/generate/serverurls/gen_test.go new file mode 100644 index 0000000000..2a2ecfb41a --- /dev/null +++ b/examples/generate/serverurls/gen_test.go @@ -0,0 +1,48 @@ +package serverurls + +import ( + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestServerUrlTheProductionAPIServer(t *testing.T) { + t.Run("when no values are provided, it does not error", func(t *testing.T) { + serverUrl, err := NewServerUrlTheProductionAPIServer("", "", "", "") + require.NoError(t, err) + + assert.Equal(t, "https://.gigantic-server.com:/", serverUrl) + + // NOTE that ideally this should fail as it doesn't /seem/ to provide a valid URL, but it does seem to be valid + _, err = url.Parse(serverUrl) + require.NoError(t, err) + }) + + // TODO:when we validate enums, this will need more testing https://github.com/oapi-codegen/oapi-codegen/issues/2006 + t.Run("when values that are not part of the enum are provided, it does not error", func(t *testing.T) { + invalidPort := ServerUrlTheProductionAPIServerPortVariable("12345") + serverUrl, err := NewServerUrlTheProductionAPIServer( + ServerUrlTheProductionAPIServerBasePathVariableDefault, + ServerUrlTheProductionAPIServerNoDefaultVariable(""), + invalidPort, + ServerUrlTheProductionAPIServerUsernameVariableDefault, + ) + require.NoError(t, err) + + assert.Equal(t, "https://demo.gigantic-server.com:12345/v2", serverUrl) + }) + + t.Run("when default values are provided, it does not error", func(t *testing.T) { + serverUrl, err := NewServerUrlTheProductionAPIServer( + ServerUrlTheProductionAPIServerBasePathVariableDefault, + ServerUrlTheProductionAPIServerNoDefaultVariable(""), + ServerUrlTheProductionAPIServerPortVariableDefault, + ServerUrlTheProductionAPIServerUsernameVariableDefault, + ) + require.NoError(t, err) + + assert.Equal(t, "https://demo.gigantic-server.com:8443/v2", serverUrl) + }) +} diff --git a/examples/generate/serverurls/generate.go b/examples/generate/serverurls/generate.go new file mode 100644 index 0000000000..68ad5cfab5 --- /dev/null +++ b/examples/generate/serverurls/generate.go @@ -0,0 +1,3 @@ +package serverurls + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/go.mod b/examples/go.mod new file mode 100644 index 0000000000..0b92bb2e87 --- /dev/null +++ b/examples/go.mod @@ -0,0 +1,126 @@ +module github.com/oapi-codegen/oapi-codegen/v2/examples + +go 1.24.3 + +replace github.com/oapi-codegen/oapi-codegen/v2 => ../ + +require ( + github.com/getkin/kin-openapi v0.133.0 + github.com/gin-gonic/gin v1.11.0 + github.com/go-chi/chi/v5 v5.2.5 + github.com/gofiber/fiber/v2 v2.52.12 + github.com/google/uuid v1.6.0 + github.com/gorilla/mux v1.8.1 + github.com/kataras/iris/v12 v12.2.11 + github.com/labstack/echo/v4 v4.15.1 + github.com/lestrrat-go/jwx v1.2.31 + github.com/oapi-codegen/echo-middleware v1.0.2 + github.com/oapi-codegen/fiber-middleware v1.0.2 + github.com/oapi-codegen/gin-middleware v1.0.2 + github.com/oapi-codegen/iris-middleware v1.0.5 + github.com/oapi-codegen/nethttp-middleware v1.1.2 + github.com/oapi-codegen/oapi-codegen/v2 v2.0.0-00010101000000-000000000000 + github.com/oapi-codegen/runtime v1.2.0 + github.com/oapi-codegen/testutil v1.1.0 + github.com/stretchr/testify v1.11.1 + golang.org/x/lint v0.0.0-20241112194109-818c5a804067 +) + +require ( + github.com/BurntSushi/toml v1.3.2 // indirect + github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect + github.com/CloudyKit/jet/v6 v6.2.0 // indirect + github.com/Joker/jade v1.1.3 // indirect + github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/aymerick/douceur v0.2.0 // indirect + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect + github.com/fatih/structs v1.1.0 // indirect + github.com/flosch/pongo2/v4 v4.0.2 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/goccy/go-json v0.10.3 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0 // indirect + github.com/gorilla/css v1.0.0 // indirect + github.com/iris-contrib/schema v0.0.6 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kataras/blocks v0.0.8 // indirect + github.com/kataras/golog v0.1.11 // indirect + github.com/kataras/pio v0.0.13 // indirect + github.com/kataras/sitemap v0.0.6 // indirect + github.com/kataras/tunnel v0.0.4 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect + github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect + github.com/mailgun/raymond/v2 v2.0.48 // indirect + github.com/mailru/easyjson v0.9.1 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/microcosm-cc/bluemonday v1.0.26 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.54.0 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/schollz/closestmatch v2.1.0+incompatible // indirect + github.com/sirupsen/logrus v1.9.1 // indirect + github.com/speakeasy-api/jsonpath v0.6.0 // indirect + github.com/speakeasy-api/openapi-overlay v0.10.3 // indirect + github.com/tdewolff/minify/v2 v2.20.19 // indirect + github.com/tdewolff/parse/v2 v2.7.12 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.51.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect + github.com/yosssi/ace v0.0.5 // indirect + go.uber.org/mock v0.5.0 // indirect + golang.org/x/arch v0.20.0 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/net v0.50.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.42.0 // indirect + google.golang.org/protobuf v1.36.9 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/examples/go.sum b/examples/go.sum new file mode 100644 index 0000000000..180de2d19d --- /dev/null +++ b/examples/go.sum @@ -0,0 +1,421 @@ +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= +github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME= +github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= +github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc= +github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= +github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk= +github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0= +github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM= +github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= +github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= +github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/gofiber/fiber/v2 v2.52.12 h1:0LdToKclcPOj8PktUdIKo9BUohjjwfnQl42Dhw8/WUw= +github.com/gofiber/fiber/v2 v2.52.12/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0 h1:4gjrh/PN2MuWCCElk8/I4OCKRKWCCo2zEct3VKCbibU= +github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/iris-contrib/httpexpect/v2 v2.15.2 h1:T9THsdP1woyAqKHwjkEsbCnMefsAFvk8iJJKokcJ3Go= +github.com/iris-contrib/httpexpect/v2 v2.15.2/go.mod h1:JLDgIqnFy5loDSUv1OA2j0mb6p/rDhiCqigP22Uq9xE= +github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw= +github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/kataras/blocks v0.0.8 h1:MrpVhoFTCR2v1iOOfGng5VJSILKeZZI+7NGfxEh3SUM= +github.com/kataras/blocks v0.0.8/go.mod h1:9Jm5zx6BB+06NwA+OhTbHW1xkMOYxahnqTN5DveZ2Yg= +github.com/kataras/golog v0.1.11 h1:dGkcCVsIpqiAMWTlebn/ZULHxFvfG4K43LF1cNWSh20= +github.com/kataras/golog v0.1.11/go.mod h1:mAkt1vbPowFUuUGvexyQ5NFW6djEgGyxQBIARJ0AH4A= +github.com/kataras/iris/v12 v12.2.11 h1:sGgo43rMPfzDft8rjVhPs6L3qDJy3TbBrMD/zGL1pzk= +github.com/kataras/iris/v12 v12.2.11/go.mod h1:uMAeX8OqG9vqdhyrIPv8Lajo/wXTtAF43wchP9WHt2w= +github.com/kataras/pio v0.0.13 h1:x0rXVX0fviDTXOOLOmr4MUxOabu1InVSTu5itF8CXCM= +github.com/kataras/pio v0.0.13/go.mod h1:k3HNuSw+eJ8Pm2lA4lRhg3DiCjVgHlP8hmXApSej3oM= +github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY= +github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4= +github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA= +github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/labstack/echo/v4 v4.15.1 h1:S9keusg26gZpjMmPqB5hOEvNKnmd1lNmcHrbbH2lnFs= +github.com/labstack/echo/v4 v4.15.1/go.mod h1:xmw1clThob0BSVRX1CRQkGQ/vjwcpOMjQZSZa9fKA/c= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= +github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx v1.2.31 h1:/OM9oNl/fzyldpv5HKZ9m7bTywa7COUfg8gujd9nJ54= +github.com/lestrrat-go/jwx v1.2.31/go.mod h1:eQJKoRwWcLg4PfD5CFA5gIZGxhPgoPYq9pZISdxLf0c= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= +github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= +github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/oapi-codegen/echo-middleware v1.0.2 h1:oNBqiE7jd/9bfGNk/bpbX2nqWrtPc+LL4Boya8Wl81U= +github.com/oapi-codegen/echo-middleware v1.0.2/go.mod h1:5J6MFcGqrpWLXpbKGZtRPZViLIHyyyUHlkqg6dT2R4E= +github.com/oapi-codegen/fiber-middleware v1.0.2 h1:f4KPdjyRTYh2GyAv9wsDP+Q9akOND17wuMSbmMwDkJI= +github.com/oapi-codegen/fiber-middleware v1.0.2/go.mod h1:+lGj+802Ajp/+fQG9d8t1SuYP8r7lnOc6wnOwwRArYg= +github.com/oapi-codegen/gin-middleware v1.0.2 h1:/H99UzvHQAUxXK8pzdcGAZgjCVeXdFDAUUWaJT0k0eI= +github.com/oapi-codegen/gin-middleware v1.0.2/go.mod h1:2HJDQjH8jzK2/k/VKcWl+/T41H7ai2bKa6dN3AA2GpA= +github.com/oapi-codegen/iris-middleware v1.0.5 h1:eO33pCvapaf1Xa0esEP0PYcdqPZSeq1eze4mamhT5hU= +github.com/oapi-codegen/iris-middleware v1.0.5/go.mod h1:/ysgvbjWyhfDAouIeUOjzIv+zsXfaIXlAQrsOU9/Kyo= +github.com/oapi-codegen/nethttp-middleware v1.1.2 h1:TQwEU3WM6ifc7ObBEtiJgbRPaCe513tvJpiMJjypVPA= +github.com/oapi-codegen/nethttp-middleware v1.1.2/go.mod h1:5qzjxMSiI8HjLljiOEjvs4RdrWyMPKnExeFS2kr8om4= +github.com/oapi-codegen/runtime v1.2.0 h1:RvKc1CVS1QeKSNzO97FBQbSMZyQ8s6rZd+LpmzwHMP4= +github.com/oapi-codegen/runtime v1.2.0/go.mod h1:Y7ZhmmlE8ikZOmuHRRndiIm7nf3xcVv+YMweKgG1DT0= +github.com/oapi-codegen/testutil v1.1.0 h1:EufqpNg43acR3qzr3ObhXmWg3Sl2kwtRnUN5GYY4d5g= +github.com/oapi-codegen/testutil v1.1.0/go.mod h1:ttCaYbHvJtHuiyeBF0tPIX+4uhEPTeizXKx28okijLw= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= +github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= +github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= +github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.1 h1:Ou41VVR3nMWWmTiEUnj0OlsgOSCUFgsPAOl6jRIcVtQ= +github.com/sirupsen/logrus v1.9.1/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8= +github.com/speakeasy-api/jsonpath v0.6.0/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw= +github.com/speakeasy-api/openapi-overlay v0.10.3 h1:70een4vwHyslIp796vM+ox6VISClhtXsCjrQNhxwvWs= +github.com/speakeasy-api/openapi-overlay v0.10.3/go.mod h1:RJjV0jbUHqXLS0/Mxv5XE7LAnJHqHw+01RDdpoGqiyY= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tdewolff/minify/v2 v2.20.19 h1:tX0SR0LUrIqGoLjXnkIzRSIbKJ7PaNnSENLD4CyH6Xo= +github.com/tdewolff/minify/v2 v2.20.19/go.mod h1:ulkFoeAVWMLEyjuDz1ZIWOA31g5aWOawCFRp9R/MudM= +github.com/tdewolff/parse/v2 v2.7.12 h1:tgavkHc2ZDEQVKy1oWxwIyh5bP4F5fEh/JmBwPP/3LQ= +github.com/tdewolff/parse/v2 v2.7.12/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA= +github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= +github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo= +github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= +github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= +github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= +github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8= +golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= +golang.org/x/lint v0.0.0-20241112194109-818c5a804067 h1:adDmSQyFTCiv19j015EGKJBoaa7ElV0Q1Wovb/4G7NA= +golang.org/x/lint v0.0.0-20241112194109-818c5a804067/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= +moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= diff --git a/examples/import-mapping/admin/api.yaml b/examples/import-mapping/admin/api.yaml new file mode 100644 index 0000000000..aeec72f407 --- /dev/null +++ b/examples/import-mapping/admin/api.yaml @@ -0,0 +1,32 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Admin API + description: The admin-only portion of the API, which has its own separate OpenAPI spec +tags: + - name: admin + description: Admin API endpoints + - name: user + description: API endpoint that pertains to user data +paths: + /admin/user/{id}: + get: + tags: + - admin + - user + summary: Get a user's details + operationId: getUserById + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '../common/api.yaml#/components/schemas/User' diff --git a/examples/import-mapping/common/api.yaml b/examples/import-mapping/common/api.yaml new file mode 100644 index 0000000000..08af65a133 --- /dev/null +++ b/examples/import-mapping/common/api.yaml @@ -0,0 +1,10 @@ +components: + schemas: + User: + type: object + additionalProperties: false + properties: + name: + type: string + required: + - name diff --git a/examples/import-mapping/multiplepackages/admin/cfg.yaml b/examples/import-mapping/multiplepackages/admin/cfg.yaml new file mode 100644 index 0000000000..65ca92f1b7 --- /dev/null +++ b/examples/import-mapping/multiplepackages/admin/cfg.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: admin +output: server.gen.go +generate: + models: true + chi-server: true +output-options: + # to make sure that all types are generated + skip-prune: true +import-mapping: + ../common/api.yaml: github.com/oapi-codegen/oapi-codegen/v2/examples/import-mapping/multiplepackages/common diff --git a/examples/import-mapping/multiplepackages/admin/generate.go b/examples/import-mapping/multiplepackages/admin/generate.go new file mode 100644 index 0000000000..304d122d22 --- /dev/null +++ b/examples/import-mapping/multiplepackages/admin/generate.go @@ -0,0 +1,3 @@ +package admin + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml ../../admin/api.yaml diff --git a/examples/import-mapping/multiplepackages/admin/server.gen.go b/examples/import-mapping/multiplepackages/admin/server.gen.go new file mode 100644 index 0000000000..36e33e4c4c --- /dev/null +++ b/examples/import-mapping/multiplepackages/admin/server.gen.go @@ -0,0 +1,184 @@ +// Package admin provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package admin + +import ( + "fmt" + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/oapi-codegen/runtime" + openapi_types "github.com/oapi-codegen/runtime/types" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Get a user's details + // (GET /admin/user/{id}) + GetUserById(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) +} + +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct{} + +// Get a user's details +// (GET /admin/user/{id}) +func (_ Unimplemented) GetUserById(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) { + w.WriteHeader(http.StatusNotImplemented) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetUserById operation middleware +func (siw *ServerInterfaceWrapper) GetUserById(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id openapi_types.UUID + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetUserById(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{}) +} + +type ChiServerOptions struct { + BaseURL string + BaseRouter chi.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = chi.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/admin/user/{id}", wrapper.GetUserById) + }) + + return r +} diff --git a/examples/import-mapping/multiplepackages/common/cfg.yaml b/examples/import-mapping/multiplepackages/common/cfg.yaml new file mode 100644 index 0000000000..8c38a4454d --- /dev/null +++ b/examples/import-mapping/multiplepackages/common/cfg.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: common +output: types.gen.go +generate: + models: true +output-options: + # to make sure that all types are generated + skip-prune: true diff --git a/examples/import-mapping/multiplepackages/common/generate.go b/examples/import-mapping/multiplepackages/common/generate.go new file mode 100644 index 0000000000..2e515e9342 --- /dev/null +++ b/examples/import-mapping/multiplepackages/common/generate.go @@ -0,0 +1,3 @@ +package common + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml ../../common/api.yaml diff --git a/examples/import-mapping/multiplepackages/common/types.gen.go b/examples/import-mapping/multiplepackages/common/types.gen.go new file mode 100644 index 0000000000..178106064e --- /dev/null +++ b/examples/import-mapping/multiplepackages/common/types.gen.go @@ -0,0 +1,9 @@ +// Package common provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package common + +// User defines model for User. +type User struct { + Name string `json:"name"` +} diff --git a/examples/import-mapping/samepackage/cfg-api.yaml b/examples/import-mapping/samepackage/cfg-api.yaml new file mode 100644 index 0000000000..76e2cb90a5 --- /dev/null +++ b/examples/import-mapping/samepackage/cfg-api.yaml @@ -0,0 +1,12 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: samepackage +output: server.gen.go +generate: + models: true + chi-server: true + strict-server: true +output-options: + # to make sure that all types are generated + skip-prune: true +import-mapping: + ../common/api.yaml: "-" diff --git a/examples/import-mapping/samepackage/cfg-user.yaml b/examples/import-mapping/samepackage/cfg-user.yaml new file mode 100644 index 0000000000..1e9d5bc39f --- /dev/null +++ b/examples/import-mapping/samepackage/cfg-user.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: samepackage +output: user.gen.go +generate: + models: true +output-options: + # to make sure that all types are generated + skip-prune: true diff --git a/examples/import-mapping/samepackage/generate.go b/examples/import-mapping/samepackage/generate.go new file mode 100644 index 0000000000..cb4ceae468 --- /dev/null +++ b/examples/import-mapping/samepackage/generate.go @@ -0,0 +1,4 @@ +package samepackage + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg-api.yaml ../admin/api.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg-user.yaml ../common/api.yaml diff --git a/examples/import-mapping/samepackage/server.gen.go b/examples/import-mapping/samepackage/server.gen.go new file mode 100644 index 0000000000..bd9445262b --- /dev/null +++ b/examples/import-mapping/samepackage/server.gen.go @@ -0,0 +1,266 @@ +// Package samepackage provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package samepackage + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/oapi-codegen/runtime" + strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp" + openapi_types "github.com/oapi-codegen/runtime/types" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Get a user's details + // (GET /admin/user/{id}) + GetUserById(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) +} + +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct{} + +// Get a user's details +// (GET /admin/user/{id}) +func (_ Unimplemented) GetUserById(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) { + w.WriteHeader(http.StatusNotImplemented) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetUserById operation middleware +func (siw *ServerInterfaceWrapper) GetUserById(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id openapi_types.UUID + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetUserById(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{}) +} + +type ChiServerOptions struct { + BaseURL string + BaseRouter chi.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = chi.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/admin/user/{id}", wrapper.GetUserById) + }) + + return r +} + +type GetUserByIdRequestObject struct { + Id openapi_types.UUID `json:"id"` +} + +type GetUserByIdResponseObject interface { + VisitGetUserByIdResponse(w http.ResponseWriter) error +} + +type GetUserById200JSONResponse User + +func (response GetUserById200JSONResponse) VisitGetUserByIdResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + // Get a user's details + // (GET /admin/user/{id}) + GetUserById(ctx context.Context, request GetUserByIdRequestObject) (GetUserByIdResponseObject, error) +} + +type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc +type StrictMiddlewareFunc = strictnethttp.StrictHTTPMiddlewareFunc + +type StrictHTTPServerOptions struct { + RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictHTTPServerOptions{ + RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + }, + ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusInternalServerError) + }, + }} +} + +func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictHTTPServerOptions) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: options} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc + options StrictHTTPServerOptions +} + +// GetUserById operation middleware +func (sh *strictHandler) GetUserById(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) { + var request GetUserByIdRequestObject + + request.Id = id + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetUserById(ctx, request.(GetUserByIdRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetUserById") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetUserByIdResponseObject); ok { + if err := validResponse.VisitGetUserByIdResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} diff --git a/examples/import-mapping/samepackage/user.gen.go b/examples/import-mapping/samepackage/user.gen.go new file mode 100644 index 0000000000..46ab646a4f --- /dev/null +++ b/examples/import-mapping/samepackage/user.gen.go @@ -0,0 +1,9 @@ +// Package samepackage provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package samepackage + +// User defines model for User. +type User struct { + Name string `json:"name"` +} diff --git a/examples/minimal-server/api.yaml b/examples/minimal-server/api.yaml new file mode 100644 index 0000000000..ccf4cd590e --- /dev/null +++ b/examples/minimal-server/api.yaml @@ -0,0 +1,25 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Minimal ping API server +paths: + /ping: + get: + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pong' +components: + schemas: + # base types + Pong: + type: object + required: + - ping + properties: + ping: + type: string + example: pong diff --git a/examples/minimal-server/chi/api/cfg.yaml b/examples/minimal-server/chi/api/cfg.yaml new file mode 100644 index 0000000000..c5df9dd559 --- /dev/null +++ b/examples/minimal-server/chi/api/cfg.yaml @@ -0,0 +1,6 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +output: ping.gen.go +generate: + models: true + chi-server: true diff --git a/examples/minimal-server/chi/api/generate.go b/examples/minimal-server/chi/api/generate.go new file mode 100644 index 0000000000..386f093dc1 --- /dev/null +++ b/examples/minimal-server/chi/api/generate.go @@ -0,0 +1,3 @@ +package api + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml ../../api.yaml diff --git a/examples/minimal-server/chi/api/impl.go b/examples/minimal-server/chi/api/impl.go new file mode 100644 index 0000000000..ddb3121e93 --- /dev/null +++ b/examples/minimal-server/chi/api/impl.go @@ -0,0 +1,25 @@ +package api + +import ( + "encoding/json" + "net/http" +) + +// ensure that we've conformed to the `ServerInterface` with a compile-time check +var _ ServerInterface = (*Server)(nil) + +type Server struct{} + +func NewServer() Server { + return Server{} +} + +// (GET /ping) +func (Server) GetPing(w http.ResponseWriter, r *http.Request) { + resp := Pong{ + Ping: "pong", + } + + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(resp) +} diff --git a/examples/minimal-server/chi/api/ping.gen.go b/examples/minimal-server/chi/api/ping.gen.go new file mode 100644 index 0000000000..a2f368005a --- /dev/null +++ b/examples/minimal-server/chi/api/ping.gen.go @@ -0,0 +1,175 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "fmt" + "net/http" + + "github.com/go-chi/chi/v5" +) + +// Pong defines model for Pong. +type Pong struct { + Ping string `json:"ping"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /ping) + GetPing(w http.ResponseWriter, r *http.Request) +} + +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct{} + +// (GET /ping) +func (_ Unimplemented) GetPing(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetPing operation middleware +func (siw *ServerInterfaceWrapper) GetPing(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetPing(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{}) +} + +type ChiServerOptions struct { + BaseURL string + BaseRouter chi.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = chi.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/ping", wrapper.GetPing) + }) + + return r +} diff --git a/examples/minimal-server/chi/main.go b/examples/minimal-server/chi/main.go new file mode 100644 index 0000000000..56e0a0515e --- /dev/null +++ b/examples/minimal-server/chi/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "log" + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/oapi-codegen/oapi-codegen/v2/examples/minimal-server/chi/api" +) + +func main() { + // create a type that satisfies the `api.ServerInterface`, which contains an implementation of every operation from the generated code + server := api.NewServer() + + r := chi.NewMux() + + // get an `http.Handler` that we can use + h := api.HandlerFromMux(server, r) + + s := &http.Server{ + Handler: h, + Addr: "0.0.0.0:8080", + } + + // And we serve HTTP until the world ends. + log.Fatal(s.ListenAndServe()) +} diff --git a/examples/minimal-server/echo/api/cfg.yaml b/examples/minimal-server/echo/api/cfg.yaml new file mode 100644 index 0000000000..06bb9233e2 --- /dev/null +++ b/examples/minimal-server/echo/api/cfg.yaml @@ -0,0 +1,6 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +output: ping.gen.go +generate: + models: true + echo-server: true diff --git a/examples/minimal-server/echo/api/generate.go b/examples/minimal-server/echo/api/generate.go new file mode 100644 index 0000000000..386f093dc1 --- /dev/null +++ b/examples/minimal-server/echo/api/generate.go @@ -0,0 +1,3 @@ +package api + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml ../../api.yaml diff --git a/examples/minimal-server/echo/api/impl.go b/examples/minimal-server/echo/api/impl.go new file mode 100644 index 0000000000..355c69bbae --- /dev/null +++ b/examples/minimal-server/echo/api/impl.go @@ -0,0 +1,25 @@ +package api + +import ( + "net/http" + + "github.com/labstack/echo/v4" +) + +// ensure that we've conformed to the `ServerInterface` with a compile-time check +var _ ServerInterface = (*Server)(nil) + +type Server struct{} + +func NewServer() Server { + return Server{} +} + +// (GET /ping) +func (Server) GetPing(ctx echo.Context) error { + resp := Pong{ + Ping: "pong", + } + + return ctx.JSON(http.StatusOK, resp) +} diff --git a/examples/minimal-server/echo/api/ping.gen.go b/examples/minimal-server/echo/api/ping.gen.go new file mode 100644 index 0000000000..bd78f8f2ad --- /dev/null +++ b/examples/minimal-server/echo/api/ping.gen.go @@ -0,0 +1,66 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "github.com/labstack/echo/v4" +) + +// Pong defines model for Pong. +type Pong struct { + Ping string `json:"ping"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /ping) + GetPing(ctx echo.Context) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// GetPing converts echo context to params. +func (w *ServerInterfaceWrapper) GetPing(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetPing(ctx) + return err +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.GET(baseURL+"/ping", wrapper.GetPing) + +} diff --git a/examples/minimal-server/echo/main.go b/examples/minimal-server/echo/main.go new file mode 100644 index 0000000000..ae53667f3e --- /dev/null +++ b/examples/minimal-server/echo/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "log" + + "github.com/labstack/echo/v4" + "github.com/oapi-codegen/oapi-codegen/v2/examples/minimal-server/echo/api" +) + +func main() { + // create a type that satisfies the `api.ServerInterface`, which contains an implementation of every operation from the generated code + server := api.NewServer() + + e := echo.New() + + api.RegisterHandlers(e, server) + + // And we serve HTTP until the world ends. + log.Fatal(e.Start("0.0.0.0:8080")) +} diff --git a/examples/minimal-server/fiber/api/cfg.yaml b/examples/minimal-server/fiber/api/cfg.yaml new file mode 100644 index 0000000000..79b6e07ac1 --- /dev/null +++ b/examples/minimal-server/fiber/api/cfg.yaml @@ -0,0 +1,6 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +output: ping.gen.go +generate: + models: true + fiber-server: true diff --git a/examples/minimal-server/fiber/api/generate.go b/examples/minimal-server/fiber/api/generate.go new file mode 100644 index 0000000000..386f093dc1 --- /dev/null +++ b/examples/minimal-server/fiber/api/generate.go @@ -0,0 +1,3 @@ +package api + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml ../../api.yaml diff --git a/examples/minimal-server/fiber/api/impl.go b/examples/minimal-server/fiber/api/impl.go new file mode 100644 index 0000000000..20bcf8478f --- /dev/null +++ b/examples/minimal-server/fiber/api/impl.go @@ -0,0 +1,27 @@ +package api + +import ( + "net/http" + + "github.com/gofiber/fiber/v2" +) + +// ensure that we've conformed to the `ServerInterface` with a compile-time check +var _ ServerInterface = (*Server)(nil) + +type Server struct{} + +func NewServer() Server { + return Server{} +} + +// (GET /ping) +func (Server) GetPing(ctx *fiber.Ctx) error { + resp := Pong{ + Ping: "pong", + } + + return ctx. + Status(http.StatusOK). + JSON(resp) +} diff --git a/examples/minimal-server/fiber/api/ping.gen.go b/examples/minimal-server/fiber/api/ping.gen.go new file mode 100644 index 0000000000..a21e6fa956 --- /dev/null +++ b/examples/minimal-server/fiber/api/ping.gen.go @@ -0,0 +1,58 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "github.com/gofiber/fiber/v2" +) + +// Pong defines model for Pong. +type Pong struct { + Ping string `json:"ping"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /ping) + GetPing(c *fiber.Ctx) error +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +type MiddlewareFunc fiber.Handler + +// GetPing operation middleware +func (siw *ServerInterfaceWrapper) GetPing(c *fiber.Ctx) error { + + return siw.Handler.GetPing(c) +} + +// FiberServerOptions provides options for the Fiber server. +type FiberServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router fiber.Router, si ServerInterface) { + RegisterHandlersWithOptions(router, si, FiberServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router fiber.Router, si ServerInterface, options FiberServerOptions) { + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + for _, m := range options.Middlewares { + router.Use(fiber.Handler(m)) + } + + router.Get(options.BaseURL+"/ping", wrapper.GetPing) + +} diff --git a/examples/minimal-server/fiber/main.go b/examples/minimal-server/fiber/main.go new file mode 100644 index 0000000000..88504af7be --- /dev/null +++ b/examples/minimal-server/fiber/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "log" + + "github.com/gofiber/fiber/v2" + "github.com/oapi-codegen/oapi-codegen/v2/examples/minimal-server/fiber/api" +) + +func main() { + // create a type that satisfies the `api.ServerInterface`, which contains an implementation of every operation from the generated code + server := api.NewServer() + + app := fiber.New() + + api.RegisterHandlers(app, server) + + // And we serve HTTP until the world ends. + log.Fatal(app.Listen("0.0.0.0:8080")) +} diff --git a/examples/minimal-server/gin/api/cfg.yaml b/examples/minimal-server/gin/api/cfg.yaml new file mode 100644 index 0000000000..951cb3f6bc --- /dev/null +++ b/examples/minimal-server/gin/api/cfg.yaml @@ -0,0 +1,6 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +output: ping.gen.go +generate: + models: true + gin-server: true diff --git a/examples/minimal-server/gin/api/generate.go b/examples/minimal-server/gin/api/generate.go new file mode 100644 index 0000000000..386f093dc1 --- /dev/null +++ b/examples/minimal-server/gin/api/generate.go @@ -0,0 +1,3 @@ +package api + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml ../../api.yaml diff --git a/examples/minimal-server/gin/api/impl.go b/examples/minimal-server/gin/api/impl.go new file mode 100644 index 0000000000..663105193f --- /dev/null +++ b/examples/minimal-server/gin/api/impl.go @@ -0,0 +1,25 @@ +package api + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +// ensure that we've conformed to the `ServerInterface` with a compile-time check +var _ ServerInterface = (*Server)(nil) + +type Server struct{} + +func NewServer() Server { + return Server{} +} + +// (GET /ping) +func (Server) GetPing(ctx *gin.Context) { + resp := Pong{ + Ping: "pong", + } + + ctx.JSON(http.StatusOK, resp) +} diff --git a/examples/minimal-server/gin/api/ping.gen.go b/examples/minimal-server/gin/api/ping.gen.go new file mode 100644 index 0000000000..8e018d4e89 --- /dev/null +++ b/examples/minimal-server/gin/api/ping.gen.go @@ -0,0 +1,72 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "github.com/gin-gonic/gin" +) + +// Pong defines model for Pong. +type Pong struct { + Ping string `json:"ping"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /ping) + GetPing(c *gin.Context) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) +} + +type MiddlewareFunc func(c *gin.Context) + +// GetPing operation middleware +func (siw *ServerInterfaceWrapper) GetPing(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.GetPing(c) +} + +// GinServerOptions provides options for the Gin server. +type GinServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router gin.IRouter, si ServerInterface) { + RegisterHandlersWithOptions(router, si, GinServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options GinServerOptions) { + errorHandler := options.ErrorHandler + if errorHandler == nil { + errorHandler = func(c *gin.Context, err error, statusCode int) { + c.JSON(statusCode, gin.H{"msg": err.Error()}) + } + } + + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandler: errorHandler, + } + + router.GET(options.BaseURL+"/ping", wrapper.GetPing) +} diff --git a/examples/minimal-server/gin/main.go b/examples/minimal-server/gin/main.go new file mode 100644 index 0000000000..365932e313 --- /dev/null +++ b/examples/minimal-server/gin/main.go @@ -0,0 +1,28 @@ +package main + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/oapi-codegen/oapi-codegen/v2/examples/minimal-server/gin/api" +) + +func main() { + // create a type that satisfies the `api.ServerInterface`, which contains an implementation of every operation from the generated code + server := api.NewServer() + + r := gin.Default() + + api.RegisterHandlers(r, server) + + // And we serve HTTP until the world ends. + + s := &http.Server{ + Handler: r, + Addr: "0.0.0.0:8080", + } + + // And we serve HTTP until the world ends. + log.Fatal(s.ListenAndServe()) +} diff --git a/examples/minimal-server/gorillamux/api/cfg.yaml b/examples/minimal-server/gorillamux/api/cfg.yaml new file mode 100644 index 0000000000..f68fe1c11d --- /dev/null +++ b/examples/minimal-server/gorillamux/api/cfg.yaml @@ -0,0 +1,6 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +output: ping.gen.go +generate: + models: true + gorilla-server: true diff --git a/examples/minimal-server/gorillamux/api/generate.go b/examples/minimal-server/gorillamux/api/generate.go new file mode 100644 index 0000000000..386f093dc1 --- /dev/null +++ b/examples/minimal-server/gorillamux/api/generate.go @@ -0,0 +1,3 @@ +package api + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml ../../api.yaml diff --git a/examples/minimal-server/gorillamux/api/impl.go b/examples/minimal-server/gorillamux/api/impl.go new file mode 100644 index 0000000000..ddb3121e93 --- /dev/null +++ b/examples/minimal-server/gorillamux/api/impl.go @@ -0,0 +1,25 @@ +package api + +import ( + "encoding/json" + "net/http" +) + +// ensure that we've conformed to the `ServerInterface` with a compile-time check +var _ ServerInterface = (*Server)(nil) + +type Server struct{} + +func NewServer() Server { + return Server{} +} + +// (GET /ping) +func (Server) GetPing(w http.ResponseWriter, r *http.Request) { + resp := Pong{ + Ping: "pong", + } + + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(resp) +} diff --git a/examples/minimal-server/gorillamux/api/ping.gen.go b/examples/minimal-server/gorillamux/api/ping.gen.go new file mode 100644 index 0000000000..9445d02b53 --- /dev/null +++ b/examples/minimal-server/gorillamux/api/ping.gen.go @@ -0,0 +1,164 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "fmt" + "net/http" + + "github.com/gorilla/mux" +) + +// Pong defines model for Pong. +type Pong struct { + Ping string `json:"ping"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /ping) + GetPing(w http.ResponseWriter, r *http.Request) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetPing operation middleware +func (siw *ServerInterfaceWrapper) GetPing(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetPing(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{}) +} + +type GorillaServerOptions struct { + BaseURL string + BaseRouter *mux.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r *mux.Router) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r *mux.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options GorillaServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = mux.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.HandleFunc(options.BaseURL+"/ping", wrapper.GetPing).Methods("GET") + + return r +} diff --git a/examples/minimal-server/gorillamux/main.go b/examples/minimal-server/gorillamux/main.go new file mode 100644 index 0000000000..5d7dc87f7f --- /dev/null +++ b/examples/minimal-server/gorillamux/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "log" + "net/http" + + "github.com/gorilla/mux" + "github.com/oapi-codegen/oapi-codegen/v2/examples/minimal-server/gorillamux/api" +) + +func main() { + // create a type that satisfies the `api.ServerInterface`, which contains an implementation of every operation from the generated code + server := api.NewServer() + + r := mux.NewRouter() + + // get an `http.Handler` that we can use + h := api.HandlerFromMux(server, r) + + s := &http.Server{ + Handler: h, + Addr: "0.0.0.0:8080", + } + + // And we serve HTTP until the world ends. + log.Fatal(s.ListenAndServe()) +} diff --git a/examples/minimal-server/iris/api/cfg.yaml b/examples/minimal-server/iris/api/cfg.yaml new file mode 100644 index 0000000000..1f83520f7e --- /dev/null +++ b/examples/minimal-server/iris/api/cfg.yaml @@ -0,0 +1,6 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +output: ping.gen.go +generate: + models: true + iris-server: true diff --git a/examples/minimal-server/iris/api/generate.go b/examples/minimal-server/iris/api/generate.go new file mode 100644 index 0000000000..386f093dc1 --- /dev/null +++ b/examples/minimal-server/iris/api/generate.go @@ -0,0 +1,3 @@ +package api + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml ../../api.yaml diff --git a/examples/minimal-server/iris/api/impl.go b/examples/minimal-server/iris/api/impl.go new file mode 100644 index 0000000000..4adbd04b3b --- /dev/null +++ b/examples/minimal-server/iris/api/impl.go @@ -0,0 +1,26 @@ +package api + +import ( + "net/http" + + "github.com/kataras/iris/v12" +) + +// ensure that we've conformed to the `ServerInterface` with a compile-time check +var _ ServerInterface = (*Server)(nil) + +type Server struct{} + +func NewServer() Server { + return Server{} +} + +// (GET /ping) +func (Server) GetPing(ctx iris.Context) { + resp := Pong{ + Ping: "pong", + } + + ctx.StatusCode(http.StatusOK) + _ = ctx.JSON(resp) +} diff --git a/examples/minimal-server/iris/api/ping.gen.go b/examples/minimal-server/iris/api/ping.gen.go new file mode 100644 index 0000000000..144166eba2 --- /dev/null +++ b/examples/minimal-server/iris/api/ping.gen.go @@ -0,0 +1,57 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "github.com/kataras/iris/v12" +) + +// Pong defines model for Pong. +type Pong struct { + Ping string `json:"ping"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /ping) + GetPing(ctx iris.Context) +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +type MiddlewareFunc iris.Handler + +// GetPing converts iris context to params. +func (w *ServerInterfaceWrapper) GetPing(ctx iris.Context) { + + // Invoke the callback with all the unmarshaled arguments + w.Handler.GetPing(ctx) +} + +// IrisServerOption is the option for iris server +type IrisServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router *iris.Application, si ServerInterface) { + RegisterHandlersWithOptions(router, si, IrisServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router *iris.Application, si ServerInterface, options IrisServerOptions) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.Get(options.BaseURL+"/ping", wrapper.GetPing) + + router.Build() +} diff --git a/examples/minimal-server/iris/main.go b/examples/minimal-server/iris/main.go new file mode 100644 index 0000000000..a163d6f598 --- /dev/null +++ b/examples/minimal-server/iris/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "log" + + "github.com/kataras/iris/v12" + "github.com/oapi-codegen/oapi-codegen/v2/examples/minimal-server/iris/api" +) + +func main() { + // create a type that satisfies the `api.ServerInterface`, which contains an implementation of every operation from the generated code + server := api.NewServer() + + i := iris.Default() + + api.RegisterHandlers(i, server) + + // And we serve HTTP until the world ends. + log.Fatal(i.Listen("0.0.0.0:8080")) +} diff --git a/examples/minimal-server/nethttp-compatible/main.go b/examples/minimal-server/nethttp-compatible/main.go new file mode 100644 index 0000000000..784b741cf0 --- /dev/null +++ b/examples/minimal-server/nethttp-compatible/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "log" + "net/http" + + "github.com/oapi-codegen/oapi-codegen/v2/examples/minimal-server/gorillamux/api" +) + +func main() { + // create a type that satisfies the `api.ServerInterface`, which contains an implementation of every operation from the generated code + server := api.NewServer() + + // get an `http.Handler` that we can use, but notice that we don't need to specify the router (although under-the-hood it'll use the generated router) + h := api.Handler(server) + + s := &http.Server{ + Handler: h, + Addr: "0.0.0.0:8080", + } + + // And we serve HTTP until the world ends. + log.Fatal(s.ListenAndServe()) +} diff --git a/examples/minimal-server/stdhttp-go-tool/api/cfg.yaml b/examples/minimal-server/stdhttp-go-tool/api/cfg.yaml new file mode 100644 index 0000000000..4369e342f4 --- /dev/null +++ b/examples/minimal-server/stdhttp-go-tool/api/cfg.yaml @@ -0,0 +1,6 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +output: ping.gen.go +generate: + models: true + std-http-server: true diff --git a/examples/minimal-server/stdhttp-go-tool/api/generate.go b/examples/minimal-server/stdhttp-go-tool/api/generate.go new file mode 100644 index 0000000000..8b01fe5ccc --- /dev/null +++ b/examples/minimal-server/stdhttp-go-tool/api/generate.go @@ -0,0 +1,3 @@ +package api + +//go:generate go tool oapi-codegen -config cfg.yaml ../../api.yaml diff --git a/examples/minimal-server/stdhttp-go-tool/api/impl.go b/examples/minimal-server/stdhttp-go-tool/api/impl.go new file mode 100644 index 0000000000..ddb3121e93 --- /dev/null +++ b/examples/minimal-server/stdhttp-go-tool/api/impl.go @@ -0,0 +1,25 @@ +package api + +import ( + "encoding/json" + "net/http" +) + +// ensure that we've conformed to the `ServerInterface` with a compile-time check +var _ ServerInterface = (*Server)(nil) + +type Server struct{} + +func NewServer() Server { + return Server{} +} + +// (GET /ping) +func (Server) GetPing(w http.ResponseWriter, r *http.Request) { + resp := Pong{ + Ping: "pong", + } + + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(resp) +} diff --git a/examples/minimal-server/stdhttp-go-tool/api/ping.gen.go b/examples/minimal-server/stdhttp-go-tool/api/ping.gen.go new file mode 100644 index 0000000000..794f8d817f --- /dev/null +++ b/examples/minimal-server/stdhttp-go-tool/api/ping.gen.go @@ -0,0 +1,171 @@ +//go:build go1.22 + +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "fmt" + "net/http" +) + +// Pong defines model for Pong. +type Pong struct { + Ping string `json:"ping"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /ping) + GetPing(w http.ResponseWriter, r *http.Request) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetPing operation middleware +func (siw *ServerInterfaceWrapper) GetPing(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetPing(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{}) +} + +// ServeMux is an abstraction of http.ServeMux. +type ServeMux interface { + HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) + ServeHTTP(w http.ResponseWriter, r *http.Request) +} + +type StdHTTPServerOptions struct { + BaseURL string + BaseRouter ServeMux + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, m ServeMux) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{ + BaseRouter: m, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, m ServeMux, baseURL string) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{ + BaseURL: baseURL, + BaseRouter: m, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.Handler { + m := options.BaseRouter + + if m == nil { + m = http.NewServeMux() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + m.HandleFunc("GET "+options.BaseURL+"/ping", wrapper.GetPing) + + return m +} diff --git a/examples/minimal-server/stdhttp-go-tool/main.go b/examples/minimal-server/stdhttp-go-tool/main.go new file mode 100644 index 0000000000..2fa48f1bf3 --- /dev/null +++ b/examples/minimal-server/stdhttp-go-tool/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "log" + "net/http" + + "github.com/oapi-codegen/oapi-codegen/v2/examples/minimal-server/stdhttp-go-tool/api" +) + +func main() { + // create a type that satisfies the `api.ServerInterface`, which contains an implementation of every operation from the generated code + server := api.NewServer() + + r := http.NewServeMux() + + // get an `http.Handler` that we can use + h := api.HandlerFromMux(server, r) + + s := &http.Server{ + Handler: h, + Addr: "0.0.0.0:8080", + } + + // And we serve HTTP until the world ends. + log.Fatal(s.ListenAndServe()) +} diff --git a/examples/minimal-server/stdhttp/Makefile b/examples/minimal-server/stdhttp/Makefile new file mode 100644 index 0000000000..5ec0edd058 --- /dev/null +++ b/examples/minimal-server/stdhttp/Makefile @@ -0,0 +1,17 @@ +lint: + $(GOBIN)/golangci-lint run ./... + +lint-ci: + $(GOBIN)/golangci-lint run ./... --output.text.path=stdout --timeout=5m + +generate: + go generate ./... + +test: + go test -cover ./... + +tidy: + go mod tidy + +tidy-ci: + tidied -verbose diff --git a/examples/minimal-server/stdhttp/api/cfg.yaml b/examples/minimal-server/stdhttp/api/cfg.yaml new file mode 100644 index 0000000000..4369e342f4 --- /dev/null +++ b/examples/minimal-server/stdhttp/api/cfg.yaml @@ -0,0 +1,6 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +output: ping.gen.go +generate: + models: true + std-http-server: true diff --git a/examples/minimal-server/stdhttp/api/generate.go b/examples/minimal-server/stdhttp/api/generate.go new file mode 100644 index 0000000000..386f093dc1 --- /dev/null +++ b/examples/minimal-server/stdhttp/api/generate.go @@ -0,0 +1,3 @@ +package api + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml ../../api.yaml diff --git a/examples/minimal-server/stdhttp/api/impl.go b/examples/minimal-server/stdhttp/api/impl.go new file mode 100644 index 0000000000..ddb3121e93 --- /dev/null +++ b/examples/minimal-server/stdhttp/api/impl.go @@ -0,0 +1,25 @@ +package api + +import ( + "encoding/json" + "net/http" +) + +// ensure that we've conformed to the `ServerInterface` with a compile-time check +var _ ServerInterface = (*Server)(nil) + +type Server struct{} + +func NewServer() Server { + return Server{} +} + +// (GET /ping) +func (Server) GetPing(w http.ResponseWriter, r *http.Request) { + resp := Pong{ + Ping: "pong", + } + + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(resp) +} diff --git a/examples/minimal-server/stdhttp/api/ping.gen.go b/examples/minimal-server/stdhttp/api/ping.gen.go new file mode 100644 index 0000000000..794f8d817f --- /dev/null +++ b/examples/minimal-server/stdhttp/api/ping.gen.go @@ -0,0 +1,171 @@ +//go:build go1.22 + +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "fmt" + "net/http" +) + +// Pong defines model for Pong. +type Pong struct { + Ping string `json:"ping"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /ping) + GetPing(w http.ResponseWriter, r *http.Request) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetPing operation middleware +func (siw *ServerInterfaceWrapper) GetPing(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetPing(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{}) +} + +// ServeMux is an abstraction of http.ServeMux. +type ServeMux interface { + HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) + ServeHTTP(w http.ResponseWriter, r *http.Request) +} + +type StdHTTPServerOptions struct { + BaseURL string + BaseRouter ServeMux + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, m ServeMux) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{ + BaseRouter: m, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, m ServeMux, baseURL string) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{ + BaseURL: baseURL, + BaseRouter: m, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.Handler { + m := options.BaseRouter + + if m == nil { + m = http.NewServeMux() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + m.HandleFunc("GET "+options.BaseURL+"/ping", wrapper.GetPing) + + return m +} diff --git a/examples/minimal-server/stdhttp/go.mod b/examples/minimal-server/stdhttp/go.mod new file mode 100644 index 0000000000..98ad587fc4 --- /dev/null +++ b/examples/minimal-server/stdhttp/go.mod @@ -0,0 +1,30 @@ +module github.com/oapi-codegen/oapi-codegen/v2/examples/minimal-server/stdhttp + +go 1.24.3 + +replace github.com/oapi-codegen/oapi-codegen/v2 => ../../../ + +require github.com/oapi-codegen/oapi-codegen/v2 v2.0.0-00010101000000-000000000000 + +require ( + github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect + github.com/getkin/kin-openapi v0.133.0 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/speakeasy-api/jsonpath v0.6.0 // indirect + github.com/speakeasy-api/openapi-overlay v0.10.3 // indirect + github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/tools v0.42.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/examples/minimal-server/stdhttp/go.sum b/examples/minimal-server/stdhttp/go.sum new file mode 100644 index 0000000000..e5ac6ef8cc --- /dev/null +++ b/examples/minimal-server/stdhttp/go.sum @@ -0,0 +1,172 @@ +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8= +github.com/speakeasy-api/jsonpath v0.6.0/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw= +github.com/speakeasy-api/openapi-overlay v0.10.3 h1:70een4vwHyslIp796vM+ox6VISClhtXsCjrQNhxwvWs= +github.com/speakeasy-api/openapi-overlay v0.10.3/go.mod h1:RJjV0jbUHqXLS0/Mxv5XE7LAnJHqHw+01RDdpoGqiyY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= +github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/minimal-server/stdhttp/main.go b/examples/minimal-server/stdhttp/main.go new file mode 100644 index 0000000000..8ee94ec49f --- /dev/null +++ b/examples/minimal-server/stdhttp/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "log" + "net/http" + + "github.com/oapi-codegen/oapi-codegen/v2/examples/minimal-server/stdhttp/api" +) + +func main() { + // create a type that satisfies the `api.ServerInterface`, which contains an implementation of every operation from the generated code + server := api.NewServer() + + r := http.NewServeMux() + + // get an `http.Handler` that we can use + h := api.HandlerFromMux(server, r) + + s := &http.Server{ + Handler: h, + Addr: "0.0.0.0:8080", + } + + // And we serve HTTP until the world ends. + log.Fatal(s.ListenAndServe()) +} diff --git a/examples/minimal-server/stdhttp/tools/tools.go b/examples/minimal-server/stdhttp/tools/tools.go new file mode 100644 index 0000000000..8615cb4c57 --- /dev/null +++ b/examples/minimal-server/stdhttp/tools/tools.go @@ -0,0 +1,8 @@ +//go:build tools +// +build tools + +package tools + +import ( + _ "github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen" +) diff --git a/examples/no-vcs-version-override/README.md b/examples/no-vcs-version-override/README.md new file mode 100644 index 0000000000..718bab670a --- /dev/null +++ b/examples/no-vcs-version-override/README.md @@ -0,0 +1,17 @@ +# Overriding the version of oapi-codegen + +## Why? + +oapi-codegen uses the standard Go means to determine what the version of the binary is. + +However, this doesn't work when there's no Version Control System (VCS), for instance when building from a source bundle. + +This example shows how to override the version at build-time. + +## How? + +By specifying `-ldflags` for the `noVCSVersionOverride` when running `go build` or `go run`: + +```sh +go run -ldflags "-X main.noVCSVersionOverride=v123.456.789" ./cmd/oapi-codegen --config=config.yaml ../../api.yaml +``` diff --git a/examples/no-vcs-version-override/api.yaml b/examples/no-vcs-version-override/api.yaml new file mode 100644 index 0000000000..2879bffe45 --- /dev/null +++ b/examples/no-vcs-version-override/api.yaml @@ -0,0 +1,14 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: NOOP API + description: An test API to check some build-time features +paths: + /nothing: + get: + operationId: getNothing + description: | + Returns nothing as expected + responses: + 200: + description: Ok diff --git a/examples/no-vcs-version-override/echo/api/api.gen.go b/examples/no-vcs-version-override/echo/api/api.gen.go new file mode 100644 index 0000000000..e95391fd13 --- /dev/null +++ b/examples/no-vcs-version-override/echo/api/api.gen.go @@ -0,0 +1,61 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v123.456.789 DO NOT EDIT. +package api + +import ( + "github.com/labstack/echo/v4" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /nothing) + GetNothing(ctx echo.Context) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// GetNothing converts echo context to params. +func (w *ServerInterfaceWrapper) GetNothing(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetNothing(ctx) + return err +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.GET(baseURL+"/nothing", wrapper.GetNothing) + +} diff --git a/examples/no-vcs-version-override/echo/api/config.yaml b/examples/no-vcs-version-override/echo/api/config.yaml new file mode 100644 index 0000000000..1f6140c6e4 --- /dev/null +++ b/examples/no-vcs-version-override/echo/api/config.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +generate: + echo-server: true +output: api.gen.go +output-options: + skip-prune: true diff --git a/examples/no-vcs-version-override/echo/api/doc.go b/examples/no-vcs-version-override/echo/api/doc.go new file mode 100644 index 0000000000..03f5a0318f --- /dev/null +++ b/examples/no-vcs-version-override/echo/api/doc.go @@ -0,0 +1,3 @@ +package api + +//go:generate go run -ldflags "-X main.noVCSVersionOverride=v123.456.789" github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../../api.yaml diff --git a/examples/only-models/api.yaml b/examples/only-models/api.yaml new file mode 100644 index 0000000000..33820e8f0b --- /dev/null +++ b/examples/only-models/api.yaml @@ -0,0 +1,50 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Generate models +paths: + /client: + get: + operationId: getClient + responses: + 200: + content: + application/json: + schema: + # NOTE that Client is generated here, because it's within #/components/schemas + $ref: "#/components/schemas/Client" + put: + operationId: updateClient + responses: + 400: + content: + application/json: + # NOTE that this anonymous object is /not/ generated because it's an anonymous, but would be generated if using `generate: client` + # See https://github.com/oapi-codegen/oapi-codegen/issues/1512 + schema: + type: object + properties: + code: + type: string + required: + - code +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + type: string + # NOTE that this is not generated by default because it's not referenced. If you want it, you need to use the following YAML configuration: + # + # output-options: + # skip-prune: true + Unreferenced: + type: object + required: + - id + properties: + id: + type: integer diff --git a/examples/only-models/cfg.yaml b/examples/only-models/cfg.yaml new file mode 100644 index 0000000000..01f4745bb7 --- /dev/null +++ b/examples/only-models/cfg.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../configuration-schema.json +package: onlymodels +output: only-models.gen.go +generate: + models: true +output-options: + # NOTE that this is only required for the `Unreferenced` type + skip-prune: true diff --git a/examples/only-models/generate.go b/examples/only-models/generate.go new file mode 100644 index 0000000000..91b4957257 --- /dev/null +++ b/examples/only-models/generate.go @@ -0,0 +1,3 @@ +package onlymodels + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/only-models/only-models.gen.go b/examples/only-models/only-models.gen.go new file mode 100644 index 0000000000..c8f33c9c8f --- /dev/null +++ b/examples/only-models/only-models.gen.go @@ -0,0 +1,14 @@ +// Package onlymodels provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package onlymodels + +// Client defines model for Client. +type Client struct { + Name string `json:"name"` +} + +// Unreferenced defines model for Unreferenced. +type Unreferenced struct { + Id int `json:"id"` +} diff --git a/examples/output-options/additionalinitialisms/api.yaml b/examples/output-options/additionalinitialisms/api.yaml new file mode 100644 index 0000000000..fbad3fc0c7 --- /dev/null +++ b/examples/output-options/additionalinitialisms/api.yaml @@ -0,0 +1,14 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: additional-initialisms +components: + schemas: + Request: + type: object + required: + - csp_name + properties: + csp_name: + description: The Cloud Service Provider's name. + type: string diff --git a/examples/output-options/additionalinitialisms/cfg.yaml b/examples/output-options/additionalinitialisms/cfg.yaml new file mode 100644 index 0000000000..9893d89ca0 --- /dev/null +++ b/examples/output-options/additionalinitialisms/cfg.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: additionalinitialisms +output: gen.go +generate: + models: true +output-options: + # to make sure that all types are generated, even if they're unreferenced + skip-prune: true + name-normalizer: ToCamelCaseWithInitialisms + additional-initialisms: + - CSP diff --git a/examples/output-options/additionalinitialisms/gen.go b/examples/output-options/additionalinitialisms/gen.go new file mode 100644 index 0000000000..950535b77b --- /dev/null +++ b/examples/output-options/additionalinitialisms/gen.go @@ -0,0 +1,10 @@ +// Package additionalinitialisms provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package additionalinitialisms + +// Request defines model for Request. +type Request struct { + // CSPName The Cloud Service Provider's name. + CSPName string `json:"csp_name"` +} diff --git a/examples/output-options/additionalinitialisms/generate.go b/examples/output-options/additionalinitialisms/generate.go new file mode 100644 index 0000000000..5b6ed9ea57 --- /dev/null +++ b/examples/output-options/additionalinitialisms/generate.go @@ -0,0 +1,3 @@ +package additionalinitialisms + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/output-options/preferskipoptionalpointer/api.yaml b/examples/output-options/preferskipoptionalpointer/api.yaml new file mode 100644 index 0000000000..8903309983 --- /dev/null +++ b/examples/output-options/preferskipoptionalpointer/api.yaml @@ -0,0 +1,67 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: prefer-skip-optional-pointer +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + description: This field is required, so will never have an optional pointer. + type: string + id: + description: This field is optional, but the `prefer-skip-optional-pointer` Output Option ensures that this should not have an optional pointer. + type: number + ClientWithExtension: + type: object + required: + - name + properties: + name: + description: This field is required, so will never have an optional pointer. + type: string + id: + description: This field is optional, but the `prefer-skip-optional-pointer` Output Option ensures that this should not have an optional pointer. + type: number + pointer_id: + type: number + description: This field should have an optional pointer, as the field-level definition of `x-go-type-skip-optional-pointer` overrides the `prefer-skip-optional-pointer` Output Option. + # NOTE that this overrides the global preference + x-go-type-skip-optional-pointer: false + NestedType: + type: object + properties: + client: + description: This field is optional, but the `prefer-skip-optional-pointer` Output Option ensures that this should not have an optional pointer. + $ref: '#/components/schemas/Client' + ReferencesATypeWithAnExtensionInsideAllOf: + type: object + description: This type has a field which references - via an `allOf` - a type which should have an optional pointer, as the field-level definition of `x-go-type-skip-optional-pointer` overrides the `prefer-skip-optional-pointer` Output Option, as well as a field without that property. + additionalProperties: false + required: + - description + properties: + noExtension: + # NOTE this will not generate pointer type as global preference `prefer-skip-optional-pointer: true` + allOf: + - $ref: '#/components/schemas/ReferencedWithoutExtension' + withExtensionPointer: + # NOTE this will generate pointer type (despite global preference `prefer-skip-optional-pointer: true`) as we have field-level `x-go-type-skip-optional-pointer: false` + allOf: + - $ref: '#/components/schemas/ReferencedWithExtension' + ReferencedWithoutExtension: + type: object + additionalProperties: false + properties: + foo: + type: string + ReferencedWithExtension: + type: object + additionalProperties: false + properties: + foo: + type: string + x-go-type-skip-optional-pointer: false diff --git a/examples/output-options/preferskipoptionalpointer/cfg.yaml b/examples/output-options/preferskipoptionalpointer/cfg.yaml new file mode 100644 index 0000000000..ae49d9a943 --- /dev/null +++ b/examples/output-options/preferskipoptionalpointer/cfg.yaml @@ -0,0 +1,9 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: preferskipoptionalpointer +output: gen.go +generate: + models: true +output-options: + # to make sure that all types are generated, even if they're unreferenced + skip-prune: true + prefer-skip-optional-pointer: true diff --git a/examples/output-options/preferskipoptionalpointer/gen.go b/examples/output-options/preferskipoptionalpointer/gen.go new file mode 100644 index 0000000000..623bf9455f --- /dev/null +++ b/examples/output-options/preferskipoptionalpointer/gen.go @@ -0,0 +1,46 @@ +// Package preferskipoptionalpointer provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package preferskipoptionalpointer + +// Client defines model for Client. +type Client struct { + // Id This field is optional, but the `prefer-skip-optional-pointer` Output Option ensures that this should not have an optional pointer. + Id float32 `json:"id,omitempty"` + + // Name This field is required, so will never have an optional pointer. + Name string `json:"name"` +} + +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension struct { + // Id This field is optional, but the `prefer-skip-optional-pointer` Output Option ensures that this should not have an optional pointer. + Id float32 `json:"id,omitempty"` + + // Name This field is required, so will never have an optional pointer. + Name string `json:"name"` + + // PointerId This field should have an optional pointer, as the field-level definition of `x-go-type-skip-optional-pointer` overrides the `prefer-skip-optional-pointer` Output Option. + PointerId *float32 `json:"pointer_id,omitempty"` +} + +// NestedType defines model for NestedType. +type NestedType struct { + Client Client `json:"client,omitempty"` +} + +// ReferencedWithExtension defines model for ReferencedWithExtension. +type ReferencedWithExtension struct { + Foo string `json:"foo,omitempty"` +} + +// ReferencedWithoutExtension defines model for ReferencedWithoutExtension. +type ReferencedWithoutExtension struct { + Foo string `json:"foo,omitempty"` +} + +// ReferencesATypeWithAnExtensionInsideAllOf This type has a field which references - via an `allOf` - a type which should have an optional pointer, as the field-level definition of `x-go-type-skip-optional-pointer` overrides the `prefer-skip-optional-pointer` Output Option, as well as a field without that property. +type ReferencesATypeWithAnExtensionInsideAllOf struct { + NoExtension ReferencedWithoutExtension `json:"noExtension,omitempty"` + WithExtensionPointer *ReferencedWithExtension `json:"withExtensionPointer,omitempty"` +} diff --git a/examples/output-options/preferskipoptionalpointer/gen_test.go b/examples/output-options/preferskipoptionalpointer/gen_test.go new file mode 100644 index 0000000000..3a5f5b4ed8 --- /dev/null +++ b/examples/output-options/preferskipoptionalpointer/gen_test.go @@ -0,0 +1,127 @@ +package preferskipoptionalpointer + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestClient(t *testing.T) { + t.Run("zero value (empty string) on Name is not omitted", func(t *testing.T) { + client := Client{ + Name: "", + } + + b, err := json.Marshal(client) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "name")) + }) + + t.Run("value on Name is not omitted", func(t *testing.T) { + client := Client{ + Name: "some value", + } + + b, err := json.Marshal(client) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "name")) + }) + + t.Run("zero value (0.0) on Id is omitted (as `omitempty` flags it as empty)", func(t *testing.T) { + client := Client{ + Id: 0.0, + } + + b, err := json.Marshal(client) + require.NoError(t, err) + + assert.False(t, jsonContainsKey(b, "id")) + }) + + t.Run("value on Id is not omitted", func(t *testing.T) { + client := Client{ + Id: 3.142, + } + + b, err := json.Marshal(client) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "id")) + }) +} + +func TestNestedType(t *testing.T) { + t.Run("zero value (empty struct) on Client is not omitted", func(t *testing.T) { + nestedType := NestedType{ + Client: Client{}, + } + + b, err := json.Marshal(nestedType) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "client")) + }) + + t.Run("value on Client is not omitted", func(t *testing.T) { + nestedType := NestedType{ + Client: Client{ + Name: "foo", + }, + } + + b, err := json.Marshal(nestedType) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "client")) + }) +} + +func TestReferencesATypeWithAnExtension(t *testing.T) { + t.Run("zero value", func(t *testing.T) { + typeWithExt := ReferencesATypeWithAnExtensionInsideAllOf{} + + assert.Zero(t, typeWithExt) + assert.Zero(t, typeWithExt.NoExtension) + assert.Nil(t, typeWithExt.WithExtensionPointer) + }) + + t.Run("value on noExtension", func(t *testing.T) { + typeWithExt := ReferencesATypeWithAnExtensionInsideAllOf{ + NoExtension: ReferencedWithoutExtension{"value"}, + WithExtensionPointer: nil, + } + + b, err := json.Marshal(typeWithExt) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "noExtension")) + assert.False(t, jsonContainsKey(b, "withExtensionPointer")) + }) + + t.Run("value on noExtension and withExtensionPointer", func(t *testing.T) { + typeWithExt := ReferencesATypeWithAnExtensionInsideAllOf{ + NoExtension: ReferencedWithoutExtension{"value"}, + WithExtensionPointer: &ReferencedWithExtension{"ptrValue"}, + } + + b, err := json.Marshal(typeWithExt) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "noExtension")) + assert.True(t, jsonContainsKey(b, "withExtensionPointer")) + }) +} + +// jsonContainsKey checks if the given JSON object contains the specified key at the top level. +func jsonContainsKey(b []byte, key string) bool { + var m map[string]any + if err := json.Unmarshal(b, &m); err != nil { + return false + } + _, ok := m[key] + return ok +} diff --git a/examples/output-options/preferskipoptionalpointer/generate.go b/examples/output-options/preferskipoptionalpointer/generate.go new file mode 100644 index 0000000000..4733fe3fe2 --- /dev/null +++ b/examples/output-options/preferskipoptionalpointer/generate.go @@ -0,0 +1,3 @@ +package preferskipoptionalpointer + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/output-options/preferskipoptionalpointerwithomitzero/api.yaml b/examples/output-options/preferskipoptionalpointerwithomitzero/api.yaml new file mode 100644 index 0000000000..d43ae9ff1f --- /dev/null +++ b/examples/output-options/preferskipoptionalpointerwithomitzero/api.yaml @@ -0,0 +1,44 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: prefer-skip-optional-pointer-with-omitzero +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + description: This field is required, so will never have an optional pointer, nor `omitzero`. + type: string + id: + description: This field is optional, but the `prefer-skip-optional-pointer` Output Option ensures that this should not have an optional pointer. However, it will receive `omitzero`. + type: number + ClientWithExtension: + type: object + required: + - name + properties: + name: + description: This field is required, so will never have an optional pointer, nor `omitzero`. + type: string + id: + description: This field is optional, but the `prefer-skip-optional-pointer` Output Option ensures that this should not have an optional pointer. However, it will receive `omitzero`. + type: number + pointer_id: + type: number + description: This field should have an optional pointer, as the field-level definition of `x-go-type-skip-optional-pointer` overrides the `prefer-skip-optional-pointer` Output Option. This will also not receive an `omitzero`. + # NOTE that this overrides the global preference + x-go-type-skip-optional-pointer: false + no_omit: + type: number + description: This field is optional, but the `prefer-skip-optional-pointer` Output Option ensures that this should not have an optional pointer. This will not receive `omitzero`, as the field-level definition of `x-omitzero` overrides the `prefer-skip-optional-pointer-with-omitzero` Output Option. + # NOTE that this overrides the global preference + x-omitzero: false + NestedType: + type: object + properties: + client: + description: This field is optional, but the `prefer-skip-optional-pointer` Output Option ensures that this should not have an optional pointer. + $ref: '#/components/schemas/Client' diff --git a/examples/output-options/preferskipoptionalpointerwithomitzero/cfg.yaml b/examples/output-options/preferskipoptionalpointerwithomitzero/cfg.yaml new file mode 100644 index 0000000000..030c49c472 --- /dev/null +++ b/examples/output-options/preferskipoptionalpointerwithomitzero/cfg.yaml @@ -0,0 +1,10 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: preferskipoptionalpointerwithomitzero +output: gen.go +generate: + models: true +output-options: + # to make sure that all types are generated, even if they're unreferenced + skip-prune: true + prefer-skip-optional-pointer: true + prefer-skip-optional-pointer-with-omitzero: true diff --git a/examples/output-options/preferskipoptionalpointerwithomitzero/gen.go b/examples/output-options/preferskipoptionalpointerwithomitzero/gen.go new file mode 100644 index 0000000000..6ea8dc2576 --- /dev/null +++ b/examples/output-options/preferskipoptionalpointerwithomitzero/gen.go @@ -0,0 +1,33 @@ +// Package preferskipoptionalpointerwithomitzero provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package preferskipoptionalpointerwithomitzero + +// Client defines model for Client. +type Client struct { + // Id This field is optional, but the `prefer-skip-optional-pointer` Output Option ensures that this should not have an optional pointer. However, it will receive `omitzero`. + Id float32 `json:"id,omitempty,omitzero"` + + // Name This field is required, so will never have an optional pointer, nor `omitzero`. + Name string `json:"name"` +} + +// ClientWithExtension defines model for ClientWithExtension. +type ClientWithExtension struct { + // Id This field is optional, but the `prefer-skip-optional-pointer` Output Option ensures that this should not have an optional pointer. However, it will receive `omitzero`. + Id float32 `json:"id,omitempty,omitzero"` + + // Name This field is required, so will never have an optional pointer, nor `omitzero`. + Name string `json:"name"` + + // NoOmit This field is optional, but the `prefer-skip-optional-pointer` Output Option ensures that this should not have an optional pointer. This will not receive `omitzero`, as the field-level definition of `x-omitzero` overrides the `prefer-skip-optional-pointer-with-omitzero` Output Option. + NoOmit float32 `json:"no_omit,omitempty"` + + // PointerId This field should have an optional pointer, as the field-level definition of `x-go-type-skip-optional-pointer` overrides the `prefer-skip-optional-pointer` Output Option. This will also not receive an `omitzero`. + PointerId *float32 `json:"pointer_id,omitempty"` +} + +// NestedType defines model for NestedType. +type NestedType struct { + Client Client `json:"client,omitempty,omitzero"` +} diff --git a/examples/output-options/preferskipoptionalpointerwithomitzero/gen_test.go b/examples/output-options/preferskipoptionalpointerwithomitzero/gen_test.go new file mode 100644 index 0000000000..d591e28616 --- /dev/null +++ b/examples/output-options/preferskipoptionalpointerwithomitzero/gen_test.go @@ -0,0 +1,91 @@ +package preferskipoptionalpointerwithomitzero + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestClient(t *testing.T) { + t.Run("zero value (empty string) on Name is not omitted", func(t *testing.T) { + client := Client{ + Name: "", + } + + b, err := json.Marshal(client) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "name")) + }) + + t.Run("value on Name is not omitted", func(t *testing.T) { + client := Client{ + Name: "some value", + } + + b, err := json.Marshal(client) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "name")) + }) + + t.Run("zero value (0.0) on Id is omitted (as `omitempty` and/or `omitzero` flags it as empty)", func(t *testing.T) { + client := Client{ + Id: 0.0, + } + + b, err := json.Marshal(client) + require.NoError(t, err) + + assert.False(t, jsonContainsKey(b, "id")) + }) + + t.Run("value on Id is not omitted", func(t *testing.T) { + client := Client{ + Id: 3.142, + } + + b, err := json.Marshal(client) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "id")) + }) +} + +func TestNestedType(t *testing.T) { + t.Run("zero value (empty struct) on Client is omitted", func(t *testing.T) { + nestedType := NestedType{ + Client: Client{}, + } + + b, err := json.Marshal(nestedType) + require.NoError(t, err) + + assert.False(t, jsonContainsKey(b, "client")) + }) + + t.Run("value on Client is not omitted", func(t *testing.T) { + nestedType := NestedType{ + Client: Client{ + Name: "foo", + }, + } + + b, err := json.Marshal(nestedType) + require.NoError(t, err) + + assert.True(t, jsonContainsKey(b, "client")) + }) +} + +// jsonContainsKey checks if the given JSON object contains the specified key at the top level. +func jsonContainsKey(b []byte, key string) bool { + var m map[string]any + if err := json.Unmarshal(b, &m); err != nil { + return false + } + _, ok := m[key] + return ok +} diff --git a/examples/output-options/preferskipoptionalpointerwithomitzero/generate.go b/examples/output-options/preferskipoptionalpointerwithomitzero/generate.go new file mode 100644 index 0000000000..08c93968c1 --- /dev/null +++ b/examples/output-options/preferskipoptionalpointerwithomitzero/generate.go @@ -0,0 +1,3 @@ +package preferskipoptionalpointerwithomitzero + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/output-options/preferskipoptionalpointerwithomitzero/tools/tools.go b/examples/output-options/preferskipoptionalpointerwithomitzero/tools/tools.go new file mode 100644 index 0000000000..8615cb4c57 --- /dev/null +++ b/examples/output-options/preferskipoptionalpointerwithomitzero/tools/tools.go @@ -0,0 +1,8 @@ +//go:build tools +// +build tools + +package tools + +import ( + _ "github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen" +) diff --git a/examples/output-options/type-mapping/config.yaml b/examples/output-options/type-mapping/config.yaml new file mode 100644 index 0000000000..7a1af00d54 --- /dev/null +++ b/examples/output-options/type-mapping/config.yaml @@ -0,0 +1,14 @@ +# yaml-language-server: $schema=../../configuration-schema.json +package: typemapping +generate: + models: true +output-options: + skip-prune: true + type-mapping: + number: + default: + type: int64 + formats: + date: + type: CustomDateHandler +output: typemapping.gen.go diff --git a/examples/output-options/type-mapping/customdate.go b/examples/output-options/type-mapping/customdate.go new file mode 100644 index 0000000000..fda97a2d3e --- /dev/null +++ b/examples/output-options/type-mapping/customdate.go @@ -0,0 +1,3 @@ +package typemapping + +type CustomDateHandler struct{} diff --git a/examples/output-options/type-mapping/generate.go b/examples/output-options/type-mapping/generate.go new file mode 100644 index 0000000000..8344a10709 --- /dev/null +++ b/examples/output-options/type-mapping/generate.go @@ -0,0 +1,6 @@ +package typemapping + +// The configuration in this directory overrides the default handling of +// "type: number" from producing an `int` to producing an `int64`, and we +// override `type: string, format: date` to be a custom type in this package. +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/examples/output-options/type-mapping/spec.yaml b/examples/output-options/type-mapping/spec.yaml new file mode 100644 index 0000000000..2c8f63487a --- /dev/null +++ b/examples/output-options/type-mapping/spec.yaml @@ -0,0 +1,18 @@ +openapi: "3.0.1" +info: + version: 1.0.0 + title: Type mapping test +paths: {} +components: + schemas: + EmployeeDatabaseRecord: + type: object + required: + - ID + - DateHired + properties: + ID: + type: number + DateHired: + type: number + format: date diff --git a/examples/output-options/type-mapping/typemapping.gen.go b/examples/output-options/type-mapping/typemapping.gen.go new file mode 100644 index 0000000000..744cab7f9c --- /dev/null +++ b/examples/output-options/type-mapping/typemapping.gen.go @@ -0,0 +1,10 @@ +// Package typemapping provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package typemapping + +// EmployeeDatabaseRecord defines model for EmployeeDatabaseRecord. +type EmployeeDatabaseRecord struct { + DateHired CustomDateHandler `json:"DateHired"` + ID int64 `json:"ID"` +} diff --git a/examples/overlay/api/api.yaml b/examples/overlay/api/api.yaml new file mode 100644 index 0000000000..367ffb19e8 --- /dev/null +++ b/examples/overlay/api/api.yaml @@ -0,0 +1,91 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: "Example to indicate how to use the OpenAPI Overlay specification (https://github.com/OAI/Overlay-Specification)" +paths: + /ping: + get: + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pong' + delete: + x-internal: true + responses: + '202': + content: {} + /admin/autoscaling: + get: + # this is a method-level `tags` + tags: + - internal + responses: + '200': + content: + application/json: + schema: + type: object + properties: + instances: + type: number + required: + - instances + /healthz: + x-internal: true + get: + responses: + '200': + content: {} + /admin/users/reset-password: + x-internal: true + put: + requestBody: + content: + application/json: + schema: + type: object + properties: + username: + type: string + not_documented: + type: string + format: uuid + x-internal: true + required: + - username + responses: + '200': + content: + application/json: + schema: + type: object + properties: + password: + type: string + not_documented: + type: string + format: uuid + x-internal: true + required: + - password +components: + schemas: + # base types + Pong: + type: object + required: + - ping + properties: + ping: + type: string + example: pong + seed: + type: number + description: The seed for the internal randomness. SHOULD NOT be explained to users + # undocumented and not useful + verbose: + type: boolean + x-internal: true diff --git a/examples/overlay/api/cfg.yaml b/examples/overlay/api/cfg.yaml new file mode 100644 index 0000000000..b506fc5679 --- /dev/null +++ b/examples/overlay/api/cfg.yaml @@ -0,0 +1,10 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: api +output: ping.gen.go +generate: + models: true + gorilla-server: true + embedded-spec: true +output-options: + overlay: + path: overlay.yaml diff --git a/examples/overlay/api/generate.go b/examples/overlay/api/generate.go new file mode 100644 index 0000000000..c025a4570d --- /dev/null +++ b/examples/overlay/api/generate.go @@ -0,0 +1,3 @@ +package api + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/overlay/api/impl.go b/examples/overlay/api/impl.go new file mode 100644 index 0000000000..4224ef2fe7 --- /dev/null +++ b/examples/overlay/api/impl.go @@ -0,0 +1,25 @@ +package api + +import ( + "encoding/json" + "net/http" +) + +// ensure that we've conformed to the `ServerInterface` with a compile-time check +var _ ServerInterface = (*Server)(nil) + +type Server struct{} + +func NewServer() Server { + return Server{} +} + +// (GET /ping) +func (Server) GetPing(w http.ResponseWriter, r *http.Request) { + resp := OverriddenPong{ + Ping: "pong", + } + + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(resp) +} diff --git a/examples/overlay/api/overlay.yaml b/examples/overlay/api/overlay.yaml new file mode 100644 index 0000000000..8ee1d72fd5 --- /dev/null +++ b/examples/overlay/api/overlay.yaml @@ -0,0 +1,65 @@ +overlay: 1.0.0 +info: + title: "Example to indicate how to use the OpenAPI Overlay specification (https://github.com/OAI/Overlay-Specification)" + version: 1.0.0 +actions: +#################################################################################################### +# Structured Overlays, via https://github.com/OAI/Overlay-Specification/blob/main/versions/1.0.0.md#examples +- target: "$" + description: Perform a structural overlay, which can be more readable, as it's clear what the shape of the document is + update: + info: + x-overlay-applied: structured-overlay + paths: + /ping: + get: + responses: + '200': + description: Perform a ping request + +#################################################################################################### +# Wildcard/Targeted overlays, via https://github.com/OAI/Overlay-Specification/blob/main/versions/1.0.0.md#examples +# +- target: $.paths.*.*[?(!@.servers)] + description: Override the servers + update: + servers: + - url: http://localhost:35123 + description: The default server. +- target: $.components.schemas.Pong + description: Override the Pong schema to utilise the `x-go-name` to override the generated Go type name + update: + x-go-name: OverriddenPong + +- target: $.paths['/ping'].get + description: Override the poorly documented internal description for the ping API + update: + description: Check that the API is running OK + +- target: $.components.schemas.Pong.properties.seed + description: Hide information about the Seed parameter + remove: true + +- target: $.components.schemas.*.*.*[?(@.x-internal)] + description: Remove any internal fields on Schemas (noted by x-internal) + remove: true + +- target: $.paths.*.*.requestBody.*.*.schema..[?(@.x-internal)] + description: Remove any internal fields on request bodies (noted by x-internal) + remove: true + +- target: $.paths.*.*.responses.*.*.*.schema..[?(@.x-internal)] + description: Remove any internal fields on responses (noted by x-internal) + remove: true + +- target: $.paths.*[?(@.x-internal)] + description: Remove internal endpoints (noted by x-internal) + remove: true + +- target: $.paths.*.*[?(@.x-internal)] + description: Remove internal endpoints (noted by x-internal) + remove: true + +- target: $.paths.*.*[?(@.tags[*] == 'internal')] + description: Remove internal endpoints (noted by internal tag) + remove: true diff --git a/examples/overlay/api/ping.gen.go b/examples/overlay/api/ping.gen.go new file mode 100644 index 0000000000..d36bdd55b3 --- /dev/null +++ b/examples/overlay/api/ping.gen.go @@ -0,0 +1,256 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/gorilla/mux" +) + +// OverriddenPong defines model for Pong. +type OverriddenPong struct { + Ping string `json:"ping"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /ping) + GetPing(w http.ResponseWriter, r *http.Request) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetPing operation middleware +func (siw *ServerInterfaceWrapper) GetPing(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetPing(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{}) +} + +type GorillaServerOptions struct { + BaseURL string + BaseRouter *mux.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r *mux.Router) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r *mux.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options GorillaServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = mux.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.HandleFunc(options.BaseURL+"/ping", wrapper.GetPing).Methods("GET") + + return r +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/1xSwW7UMBD9FWvgAFJ2ve2Ki28VQmjFYVeCW9WD60xil8Rj7ElpVfnf0Ti7FHqaePLs", + "997MewFHc6KIkQuYFyjO42zb54niKDVlSpg5YOumsHbxyc5pQjCQBNcBPyc5Fc6CqLWDjL+WkLEHc7te", + "u/uLovsHdAwdPG1G2kQ7S/P4iDmHvsfYqKu8EeJAQseBG9mXlVYxqRD74Cyj8vRbzktBxR7VMWG8OR2U", + "PDfZZ1USujAINFBUHzxzKkbrMbBf7reOZn28OegzevP9X/RH6OARcwkUwcDVdrfdNc10BtuUpiAGxfbi", + "eMnYX35C7YASRpsCGNif7ybLvs1R234OUduFqTg7rVOtHejLgEdkKT0Wl0PiVcJnj+6nYm+5WRWboai8", + "xBjiqI7foHHmJv4gur4in0LbT8aSKJZ1i9e7nRRHkTE2nmZlda0fipBdwiBf7zMOYOCdfk2LPkdFvy7r", + "f60nzAPlWVkllpSkAQu3ZBTMMlYwt28N/vCoehzsMrFaUVvoYMkTGJDNGa0ncnbyVNjsP11d76He1Vrr", + "nwAAAP//QsRK0MkCAAA=", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/examples/overlay/main.go b/examples/overlay/main.go new file mode 100644 index 0000000000..34cfb71efd --- /dev/null +++ b/examples/overlay/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "log" + "net/http" + + "github.com/gorilla/mux" + "github.com/oapi-codegen/oapi-codegen/v2/examples/overlay/api" +) + +func main() { + // create a type that satisfies the `api.ServerInterface`, which contains an implementation of every operation from the generated code + server := api.NewServer() + + r := mux.NewRouter() + + // get an `http.Handler` that we can use + h := api.HandlerFromMux(server, r) + + s := &http.Server{ + Handler: h, + Addr: "0.0.0.0:8080", + } + + // And we serve HTTP until the world ends. + log.Fatal(s.ListenAndServe()) +} diff --git a/examples/petstore-expanded/chi/api/cfg.yaml b/examples/petstore-expanded/chi/api/cfg.yaml index d1a29f3374..4191a3047d 100644 --- a/examples/petstore-expanded/chi/api/cfg.yaml +++ b/examples/petstore-expanded/chi/api/cfg.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: api generate: chi-server: true diff --git a/examples/petstore-expanded/chi/api/petstore.gen.go b/examples/petstore-expanded/chi/api/petstore.gen.go index 66505c564f..88b246f37b 100644 --- a/examples/petstore-expanded/chi/api/petstore.gen.go +++ b/examples/petstore-expanded/chi/api/petstore.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api import ( @@ -13,9 +13,9 @@ import ( "path" "strings" - "github.com/deepmap/oapi-codegen/pkg/runtime" "github.com/getkin/kin-openapi/openapi3" "github.com/go-chi/chi/v5" + "github.com/oapi-codegen/runtime" ) // Error defines model for Error. @@ -76,6 +76,34 @@ type ServerInterface interface { FindPetByID(w http.ResponseWriter, r *http.Request, id int64) } +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct{} + +// Returns all pets +// (GET /pets) +func (_ Unimplemented) FindPets(w http.ResponseWriter, r *http.Request, params FindPetsParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Creates a new pet +// (POST /pets) +func (_ Unimplemented) AddPet(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Deletes a pet by ID +// (DELETE /pets/{id}) +func (_ Unimplemented) DeletePet(w http.ResponseWriter, r *http.Request, id int64) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Returns a pet by ID +// (GET /pets/{id}) +func (_ Unimplemented) FindPetByID(w http.ResponseWriter, r *http.Request, id int64) { + w.WriteHeader(http.StatusNotImplemented) +} + // ServerInterfaceWrapper converts contexts to parameters. type ServerInterfaceWrapper struct { Handler ServerInterface @@ -87,7 +115,6 @@ type MiddlewareFunc func(http.Handler) http.Handler // FindPets operation middleware func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() var err error @@ -110,82 +137,79 @@ func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Reque return } - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.FindPets(w, r, params) - }) + })) for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- { handler = siw.HandlerMiddlewares[i](handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // AddPet operation middleware func (siw *ServerInterfaceWrapper) AddPet(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.AddPet(w, r) - }) + })) for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- { handler = siw.HandlerMiddlewares[i](handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // DeletePet operation middleware func (siw *ServerInterfaceWrapper) DeletePet(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() var err error // ------------- Path parameter "id" ------------- var id int64 - err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, chi.URLParam(r, "id"), &id) + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) return } - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.DeletePet(w, r, id) - }) + })) for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- { handler = siw.HandlerMiddlewares[i](handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // FindPetByID operation middleware func (siw *ServerInterfaceWrapper) FindPetByID(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() var err error // ------------- Path parameter "id" ------------- var id int64 - err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, chi.URLParam(r, "id"), &id) + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) return } - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.FindPetByID(w, r, id) - }) + })) for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- { handler = siw.HandlerMiddlewares[i](handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } type UnescapedCookieParamError struct { @@ -201,16 +225,16 @@ func (e *UnescapedCookieParamError) Unwrap() error { return e.Err } -type UnmarshallingParamError struct { +type UnmarshalingParamError struct { ParamName string Err error } -func (e *UnmarshallingParamError) Error() string { - return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) } -func (e *UnmarshallingParamError) Unwrap() error { +func (e *UnmarshalingParamError) Unwrap() error { return e.Err } @@ -355,16 +379,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -382,7 +406,7 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } @@ -396,12 +420,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/examples/petstore-expanded/chi/api/petstore.go b/examples/petstore-expanded/chi/api/petstore.go index 838c961165..6ede559ec5 100644 --- a/examples/petstore-expanded/chi/api/petstore.go +++ b/examples/petstore-expanded/chi/api/petstore.go @@ -1,4 +1,4 @@ -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=cfg.yaml ../../petstore-expanded.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=cfg.yaml ../../petstore-expanded.yaml package api @@ -26,7 +26,7 @@ func NewPetStore() *PetStore { } } -// This function wraps sending of an error in the Error format, and +// sendPetStoreError wraps sending of an error in the Error format, and // handling the failure to marshal that. func sendPetStoreError(w http.ResponseWriter, code int, message string) { petErr := Error{ @@ -34,7 +34,7 @@ func sendPetStoreError(w http.ResponseWriter, code int, message string) { Message: message, } w.WriteHeader(code) - json.NewEncoder(w).Encode(petErr) + _ = json.NewEncoder(w).Encode(petErr) } // FindPets implements all the handlers in the ServerInterface @@ -67,7 +67,7 @@ func (p *PetStore) FindPets(w http.ResponseWriter, r *http.Request, params FindP } w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(result) + _ = json.NewEncoder(w).Encode(result) } func (p *PetStore) AddPet(w http.ResponseWriter, r *http.Request) { @@ -89,14 +89,14 @@ func (p *PetStore) AddPet(w http.ResponseWriter, r *http.Request) { pet.Name = newPet.Name pet.Tag = newPet.Tag pet.Id = p.NextId - p.NextId = p.NextId + 1 + p.NextId++ // Insert into map p.Pets[pet.Id] = pet // Now, we have to return the NewPet w.WriteHeader(http.StatusCreated) - json.NewEncoder(w).Encode(pet) + _ = json.NewEncoder(w).Encode(pet) } func (p *PetStore) FindPetByID(w http.ResponseWriter, r *http.Request, id int64) { @@ -110,7 +110,7 @@ func (p *PetStore) FindPetByID(w http.ResponseWriter, r *http.Request, id int64) } w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(pet) + _ = json.NewEncoder(w).Encode(pet) } func (p *PetStore) DeletePet(w http.ResponseWriter, r *http.Request, id int64) { diff --git a/examples/petstore-expanded/chi/petstore.go b/examples/petstore-expanded/chi/petstore.go index cdf725e9b3..b525fa5763 100644 --- a/examples/petstore-expanded/chi/petstore.go +++ b/examples/petstore-expanded/chi/petstore.go @@ -8,16 +8,17 @@ import ( "flag" "fmt" "log" + "net" "net/http" "os" - "github.com/deepmap/oapi-codegen/examples/petstore-expanded/chi/api" - middleware "github.com/deepmap/oapi-codegen/pkg/chi-middleware" "github.com/go-chi/chi/v5" + middleware "github.com/oapi-codegen/nethttp-middleware" + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/chi/api" ) func main() { - var port = flag.Int("port", 8080, "Port for test HTTP server") + port := flag.String("port", "8080", "Port for test HTTP server") flag.Parse() swagger, err := api.GetSwagger() @@ -45,7 +46,7 @@ func main() { s := &http.Server{ Handler: r, - Addr: fmt.Sprintf("0.0.0.0:%d", *port), + Addr: net.JoinHostPort("0.0.0.0", *port), } // And we serve HTTP until the world ends. diff --git a/examples/petstore-expanded/chi/petstore_test.go b/examples/petstore-expanded/chi/petstore_test.go index e0e9a88ca3..e9d7662b45 100644 --- a/examples/petstore-expanded/chi/petstore_test.go +++ b/examples/petstore-expanded/chi/petstore_test.go @@ -7,10 +7,10 @@ import ( "net/http/httptest" "testing" - "github.com/deepmap/oapi-codegen/examples/petstore-expanded/chi/api" - middleware "github.com/deepmap/oapi-codegen/pkg/chi-middleware" - "github.com/deepmap/oapi-codegen/pkg/testutil" "github.com/go-chi/chi/v5" + middleware "github.com/oapi-codegen/nethttp-middleware" + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/chi/api" + "github.com/oapi-codegen/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -53,7 +53,7 @@ func TestPetStore(t *testing.T) { var resultPet api.Pet err = json.NewDecoder(rr.Body).Decode(&resultPet) - assert.NoError(t, err, "error unmarshalling response") + assert.NoError(t, err, "error unmarshaling response") assert.Equal(t, newPet.Name, resultPet.Name) assert.Equal(t, *newPet.Tag, *resultPet.Tag) }) @@ -146,7 +146,7 @@ func TestPetStore(t *testing.T) { var petError api.Error err = json.NewDecoder(rr.Body).Decode(&petError) - assert.NoError(t, err, "error unmarshalling PetError") + assert.NoError(t, err, "error unmarshaling PetError") assert.Equal(t, int32(http.StatusNotFound), petError.Code) // Now, delete both real pets diff --git a/examples/petstore-expanded/echo-v5/Makefile b/examples/petstore-expanded/echo-v5/Makefile new file mode 100644 index 0000000000..7eb1896df1 --- /dev/null +++ b/examples/petstore-expanded/echo-v5/Makefile @@ -0,0 +1,30 @@ +# This module requires Go 1.25+ (for echo/v5). Skip gracefully on older versions. +GO_VERSION := $(shell go version | sed 's/.*go\([0-9]*\)\.\([0-9]*\).*/\1\2/') +GO_125_OR_LATER := $(shell [ "$(GO_VERSION)" -ge 125 ] 2>/dev/null && echo yes || echo no) + +ifeq ($(GO_125_OR_LATER),yes) + +lint: + $(GOBIN)/golangci-lint run ./... + +lint-ci: + $(GOBIN)/golangci-lint run ./... --output.text.path=stdout --timeout=5m + +generate: + go generate ./... + +test: + go test -cover ./... + +tidy: + go mod tidy + +tidy-ci: + tidied -verbose + +else + +lint generate test tidy lint-ci tidy-ci: + @echo "Skipping echo-v5 example: requires Go 1.25+ (found $(shell go version))" + +endif diff --git a/examples/petstore-expanded/echo-v5/api/models.cfg.yaml b/examples/petstore-expanded/echo-v5/api/models.cfg.yaml new file mode 100644 index 0000000000..46b5e629c3 --- /dev/null +++ b/examples/petstore-expanded/echo-v5/api/models.cfg.yaml @@ -0,0 +1,5 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: models +generate: + models: true +output: models/models.gen.go diff --git a/examples/petstore-expanded/echo-v5/api/models/models.gen.go b/examples/petstore-expanded/echo-v5/api/models/models.gen.go new file mode 100644 index 0000000000..0945e02abb --- /dev/null +++ b/examples/petstore-expanded/echo-v5/api/models/models.gen.go @@ -0,0 +1,46 @@ +// Package models provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package models + +// Error defines model for Error. +type Error struct { + // Code Error code + Code int32 `json:"code"` + + // Message Error message + Message string `json:"message"` +} + +// NewPet defines model for NewPet. +type NewPet struct { + // Name Name of the pet + Name string `json:"name"` + + // Tag Type of the pet + Tag *string `json:"tag,omitempty"` +} + +// Pet defines model for Pet. +type Pet struct { + // Id Unique id of the pet + Id int64 `json:"id"` + + // Name Name of the pet + Name string `json:"name"` + + // Tag Type of the pet + Tag *string `json:"tag,omitempty"` +} + +// FindPetsParams defines parameters for FindPets. +type FindPetsParams struct { + // Tags tags to filter by + Tags *[]string `form:"tags,omitempty" json:"tags,omitempty"` + + // Limit maximum number of results to return + Limit *int32 `form:"limit,omitempty" json:"limit,omitempty"` +} + +// AddPetJSONRequestBody defines body for AddPet for application/json ContentType. +type AddPetJSONRequestBody = NewPet diff --git a/examples/petstore-expanded/echo-v5/api/petstore-server.gen.go b/examples/petstore-expanded/echo-v5/api/petstore-server.gen.go new file mode 100644 index 0000000000..c99d14e424 --- /dev/null +++ b/examples/petstore-expanded/echo-v5/api/petstore-server.gen.go @@ -0,0 +1,247 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/labstack/echo/v5" + . "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/echo-v5/api/models" + "github.com/oapi-codegen/runtime" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Returns all pets + // (GET /pets) + FindPets(ctx *echo.Context, params FindPetsParams) error + // Creates a new pet + // (POST /pets) + AddPet(ctx *echo.Context) error + // Deletes a pet by ID + // (DELETE /pets/{id}) + DeletePet(ctx *echo.Context, id int64) error + // Returns a pet by ID + // (GET /pets/{id}) + FindPetByID(ctx *echo.Context, id int64) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// FindPets converts echo context to params. +func (w *ServerInterfaceWrapper) FindPets(ctx *echo.Context) error { + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params FindPetsParams + // ------------- Optional query parameter "tags" ------------- + + err = runtime.BindQueryParameter("form", true, false, "tags", ctx.QueryParams(), ¶ms.Tags) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter tags: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.FindPets(ctx, params) + return err +} + +// AddPet converts echo context to params. +func (w *ServerInterfaceWrapper) AddPet(ctx *echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.AddPet(ctx) + return err +} + +// DeletePet converts echo context to params. +func (w *ServerInterfaceWrapper) DeletePet(ctx *echo.Context) error { + var err error + // ------------- Path parameter "id" ------------- + var id int64 + + err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.DeletePet(ctx, id) + return err +} + +// FindPetByID converts echo context to params. +func (w *ServerInterfaceWrapper) FindPetByID(ctx *echo.Context) error { + var err error + // ------------- Path parameter "id" ------------- + var id int64 + + err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.FindPetByID(ctx, id) + return err +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) echo.RouteInfo + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) echo.RouteInfo + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) echo.RouteInfo + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) echo.RouteInfo + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) echo.RouteInfo + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) echo.RouteInfo + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) echo.RouteInfo + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) echo.RouteInfo + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) echo.RouteInfo +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.GET(baseURL+"/pets", wrapper.FindPets) + router.POST(baseURL+"/pets", wrapper.AddPet) + router.DELETE(baseURL+"/pets/:id", wrapper.DeletePet) + router.GET(baseURL+"/pets/:id", wrapper.FindPetByID) + +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/+RXW48budH9KwV+32OnNbEXedBTvB4vICBrT+LdvKznoYZdkmrBSw9Z1FgY6L8HRbZu", + "I3k2QYIgQV506WY1T51zqlj9bGz0YwwUJJv5s8l2TR7rzw8pxaQ/xhRHSsJUL9s4kH4PlG3iUTgGM2+L", + "od7rzDImj2LmhoO8fWM6I9uR2l9aUTK7znjKGVfffND+9iE0S+KwMrtdZxI9Fk40mPkvZtpwv/x+15mP", + "9HRHcok7oL+y3Uf0BHEJsiYYSS437Izg6jLup+34etwLoHV3hTdhQ+c+Lc38l2fz/4mWZm7+b3YUYjap", + "MJty2XUvk+HhEtLPgR8LAQ/nuE7F+MN3V8R4gZQHc7+73+llDsvYJA+CtuImj+zM3ODIQuj/mJ9wtaLU", + "czTdRLH53K7Bu7sF/EToTWdK0qC1yJjns9lJ0K57kcU7yOhHRzVa1ihQMmVAzSZLTASYAQPQ17ZMIgzk", + "Y8iSUAiWhFISZeBQOfg0UtAnve1vII9keckW61adcWwpZDqaw7wb0a4J3vQ3F5ifnp56rLf7mFazKTbP", + "/rR4/+Hj5w+/e9Pf9GvxrjqGks+flp8pbdjS1cRndc1M5WBxp6zdTXmazmwo5cbK7/ub/kYfHUcKOLKZ", + "m7f1UmdGlHX1xEwZ0h+rZrFzXv9CUlLIgM5VKmGZoq8U5W0W8o1r/V8yJVgry9ZSziDxS/iIHjINYGMY", + "2FOQ4oGy9PAjkqWAGYT8GBNkXLEIZ8g4MoUOAllI6xhsyZDJnyxgAfQkPbyjQBgABVYJNzwgYFkV6gAt", + "MNriuIb28L4kfGApCeLAEVxM5DuIKWAioBUJkKMJXSDbgS0pl6wl4chKyT3cFs7gGaSkkXMHY3EbDph0", + "L0pRk+5AOFgeShDYYOKS4deSJfawCLBGC2sFgTkTjA6FEAa2UrzSsWhFpbngwCNny2EFGESzOebueFUc", + "HjIf15hIEu5J1PXgo6MsTMB+pDSwMvVX3qBvCaHjx4IeBkZlJmGGR81tQ44FQgwgMUlMSgkvKQyH3Xu4", + "S0iZgihMCuyPAEoKCJvoiowosKFAARVwI1c/PJakz1iE45OXlCbWl2jZcT7bpO6gH91RXws5DuhIhR06", + "5dFSQtHE9LuHzyWPFAZWlh2qeYboYurUgZmsqJtrltUqmnUHG1qzLQ5BW1saigfHD5RiDz/G9MBAhbOP", + "w6kMersa26HlwNh/CV/CZxqqEiXDktR8Lj7EVAMoHh2TiqTie9Da8FgfOJHP2XVA5axamuTgivpQ3dnD", + "3RozOdcKY6Q0hVeaq7wksMRi+aE0wnG/j647jd+Qm6TjDaWE3fnWWifAQ3coxMAP6x5+FhjJOQpCWU+O", + "MeZCWkn7IupBqcB9FWjR7bncP2mfVmWyq0AOtgglWJDEWerBtGFB6uGHki0BSe0GQ+FDFWinyJYcJa5w", + "mn/3AV7dUrCaxxafMYDHlaZMblKrhz+XFuqjU92aelSad45QukPzASxWi6StnOzZ0p7MMTWZQzWqWVRg", + "4NAdoUyFGzjzHnBWDJalDKxQc0YosvfZJGTb6Yy0ul8Pd6fCVOYmjGMi4eJPOlczTelO/K2tt/+iZ5wO", + "DfW8Wwxmbn7gMOj5Uo+NpARQynUKOT8sBFfa92HJTijBw9boMGDm5rFQ2h5Pel1numlorHOJkK9n0OUU", + "1S5gSrjV/1m29djT8aQOOOcIPH5lr228+AdKOtEkysVJhZXqWfYNTI49yxmo3xxHd/c6AuVRW0tF/+bm", + "Zj/3UGjz2ji6aXKY/ZoV4vO1tF8b5tok94KI3cUANJLAHkwbj5ZYnPxDeF6D0cb6KxuXQF9Hba3ag9ua", + "zuTiPabtlQFCsY0xXxk13idCqTNboCddux/G6lyjZ3DDrkt0nnMuPtFwYdZ3g3rVtOmUsnwfh+2/jIX9", + "ZH1Jwx2JegyHQb8OsM3plCyp0O6f9MxvWuW/xxoXgtf7dR6dPfOwaxZxJFdewNp1jc0cVq6+tcADapuN", + "zTWLW8hFc7rikdsa3Wzyakdb3GoPGZu2E5apf+gAfWwfPFwo/a1ecv1t6rKXfHeZtQJpKIb/JCFvD2JU", + "FbawuFV4r79QnCt20HFx+63j5/ttvff367Ukset/m1z/s2X8QtGmfl1CabOX6fyteP9S3p+82err6e5+", + "97cAAAD//ykDnxlaEgAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/examples/petstore-expanded/echo-v5/api/petstore.go b/examples/petstore-expanded/echo-v5/api/petstore.go new file mode 100644 index 0000000000..6313eec228 --- /dev/null +++ b/examples/petstore-expanded/echo-v5/api/petstore.go @@ -0,0 +1,146 @@ +// Copyright 2019 DeepMap, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=models.cfg.yaml ../../petstore-expanded.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=server.cfg.yaml ../../petstore-expanded.yaml + +package api + +import ( + "fmt" + "net/http" + "sync" + + "github.com/labstack/echo/v5" + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/echo-v5/api/models" +) + +type PetStore struct { + Pets map[int64]models.Pet + NextId int64 + Lock sync.Mutex +} + +func NewPetStore() *PetStore { + return &PetStore{ + Pets: make(map[int64]models.Pet), + NextId: 1000, + } +} + +// sendPetStoreError wraps sending of an error in the Error format, and +// handling the failure to marshal that. +func sendPetStoreError(ctx *echo.Context, code int, message string) error { + petErr := models.Error{ + Code: int32(code), + Message: message, + } + err := ctx.JSON(code, petErr) + return err +} + +// FindPets implements all the handlers in the ServerInterface +func (p *PetStore) FindPets(ctx *echo.Context, params models.FindPetsParams) error { + p.Lock.Lock() + defer p.Lock.Unlock() + + var result []models.Pet + + for _, pet := range p.Pets { + if params.Tags != nil { + // If we have tags, filter pets by tag + for _, t := range *params.Tags { + if pet.Tag != nil && (*pet.Tag == t) { + result = append(result, pet) + } + } + } else { + // Add all pets if we're not filtering + result = append(result, pet) + } + + if params.Limit != nil { + l := int(*params.Limit) + if len(result) >= l { + // We're at the limit + break + } + } + } + return ctx.JSON(http.StatusOK, result) +} + +func (p *PetStore) AddPet(ctx *echo.Context) error { + // We expect a NewPet object in the request body. + var newPet models.NewPet + err := ctx.Bind(&newPet) + if err != nil { + return sendPetStoreError(ctx, http.StatusBadRequest, "Invalid format for NewPet") + } + // We now have a pet, let's add it to our "database". + + // We're always asynchronous, so lock unsafe operations below + p.Lock.Lock() + defer p.Lock.Unlock() + + // We handle pets, not NewPets, which have an additional ID field + var pet models.Pet + pet.Name = newPet.Name + pet.Tag = newPet.Tag + pet.Id = p.NextId + p.NextId++ + + // Insert into map + p.Pets[pet.Id] = pet + + // Now, we have to return the NewPet + err = ctx.JSON(http.StatusCreated, pet) + if err != nil { + // Something really bad happened, tell Echo that our handler failed + return err + } + + // Return no error. This refers to the handler. Even if we return an HTTP + // error, but everything else is working properly, tell Echo that we serviced + // the error. We should only return errors from Echo handlers if the actual + // servicing of the error on the infrastructure level failed. Returning an + // HTTP/400 or HTTP/500 from here means Echo/HTTP are still working, so + // return nil. + return nil +} + +func (p *PetStore) FindPetByID(ctx *echo.Context, petId int64) error { + p.Lock.Lock() + defer p.Lock.Unlock() + + pet, found := p.Pets[petId] + if !found { + return sendPetStoreError(ctx, http.StatusNotFound, + fmt.Sprintf("Could not find pet with ID %d", petId)) + } + return ctx.JSON(http.StatusOK, pet) +} + +func (p *PetStore) DeletePet(ctx *echo.Context, id int64) error { + p.Lock.Lock() + defer p.Lock.Unlock() + + _, found := p.Pets[id] + if !found { + return sendPetStoreError(ctx, http.StatusNotFound, + fmt.Sprintf("Could not find pet with ID %d", id)) + } + delete(p.Pets, id) + return ctx.NoContent(http.StatusNoContent) +} diff --git a/examples/petstore-expanded/echo-v5/api/server.cfg.yaml b/examples/petstore-expanded/echo-v5/api/server.cfg.yaml new file mode 100644 index 0000000000..478dc9fc99 --- /dev/null +++ b/examples/petstore-expanded/echo-v5/api/server.cfg.yaml @@ -0,0 +1,9 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +output: petstore-server.gen.go +additional-imports: + - package: github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/echo-v5/api/models + alias: . +generate: + echo5-server: true + embedded-spec: true diff --git a/examples/petstore-expanded/echo-v5/go.mod b/examples/petstore-expanded/echo-v5/go.mod new file mode 100644 index 0000000000..090320989f --- /dev/null +++ b/examples/petstore-expanded/echo-v5/go.mod @@ -0,0 +1,42 @@ +module github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/echo-v5 + +go 1.25.0 + +replace github.com/oapi-codegen/oapi-codegen/v2 => ../../../ + +require ( + github.com/getkin/kin-openapi v0.133.0 + github.com/labstack/echo/v5 v5.0.4 + github.com/oapi-codegen/oapi-codegen/v2 v2.0.0-00010101000000-000000000000 + github.com/oapi-codegen/runtime v1.2.0 + github.com/oapi-codegen/testutil v1.1.0 + github.com/stretchr/testify v1.11.1 +) + +require ( + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/google/uuid v1.5.0 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/speakeasy-api/jsonpath v0.6.0 // indirect + github.com/speakeasy-api/openapi-overlay v0.10.2 // indirect + github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.42.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/examples/petstore-expanded/echo-v5/go.sum b/examples/petstore-expanded/echo-v5/go.sum new file mode 100644 index 0000000000..6cb9a23cb9 --- /dev/null +++ b/examples/petstore-expanded/echo-v5/go.sum @@ -0,0 +1,191 @@ +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/labstack/echo/v5 v5.0.4 h1:ll3I/O8BifjMztj9dD1vx/peZQv8cR2CTUdQK6QxGGc= +github.com/labstack/echo/v5 v5.0.4/go.mod h1:SyvlSdObGjRXeQfCCXW/sybkZdOOQZBmpKF0bvALaeo= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oapi-codegen/runtime v1.2.0 h1:RvKc1CVS1QeKSNzO97FBQbSMZyQ8s6rZd+LpmzwHMP4= +github.com/oapi-codegen/runtime v1.2.0/go.mod h1:Y7ZhmmlE8ikZOmuHRRndiIm7nf3xcVv+YMweKgG1DT0= +github.com/oapi-codegen/testutil v1.1.0 h1:EufqpNg43acR3qzr3ObhXmWg3Sl2kwtRnUN5GYY4d5g= +github.com/oapi-codegen/testutil v1.1.0/go.mod h1:ttCaYbHvJtHuiyeBF0tPIX+4uhEPTeizXKx28okijLw= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8= +github.com/speakeasy-api/jsonpath v0.6.0/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw= +github.com/speakeasy-api/openapi-overlay v0.10.2 h1:VOdQ03eGKeiHnpb1boZCGm7x8Haj6gST0P3SGTX95GU= +github.com/speakeasy-api/openapi-overlay v0.10.2/go.mod h1:n0iOU7AqKpNFfEt6tq7qYITC4f0yzVVdFw0S7hukemg= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= +github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/petstore-expanded/echo-v5/middleware/middleware.go b/examples/petstore-expanded/echo-v5/middleware/middleware.go new file mode 100644 index 0000000000..0a241fbcf6 --- /dev/null +++ b/examples/petstore-expanded/echo-v5/middleware/middleware.go @@ -0,0 +1,226 @@ +// Package middleware provides OpenAPI request validation middleware for Echo v5. +// +// Adapted from github.com/oapi-codegen/echo-middleware (Echo v4) for use +// with github.com/labstack/echo/v5, which has breaking API changes +// (pointer context, NewHTTPError signature, no HTTPError.Internal field). +// +// This is intentionally inlined in the example because there is no published +// echo-v5 middleware package yet. +// TODO: make an echo-v5 middleware repo +package middleware + +import ( + "context" + "errors" + "fmt" + "log" + "net/http" + "net/url" + "os" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/openapi3filter" + "github.com/getkin/kin-openapi/routers" + "github.com/getkin/kin-openapi/routers/gorillamux" + "github.com/labstack/echo/v5" + echomiddleware "github.com/labstack/echo/v5/middleware" +) + +const ( + EchoContextKey = "oapi-codegen/echo-context" + UserDataKey = "oapi-codegen/user-data" +) + +// OapiValidatorFromYamlFile creates validator middleware from a YAML file path. +func OapiValidatorFromYamlFile(path string) (echo.MiddlewareFunc, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("error reading %s: %w", path, err) + } + + spec, err := openapi3.NewLoader().LoadFromData(data) + if err != nil { + return nil, fmt.Errorf("error parsing %s as OpenAPI YAML: %w", path, err) + } + return OapiRequestValidator(spec), nil +} + +// OapiRequestValidator creates middleware to validate incoming requests against +// the given OpenAPI 3.x spec with default configuration. +func OapiRequestValidator(spec *openapi3.T) echo.MiddlewareFunc { + return OapiRequestValidatorWithOptions(spec, nil) +} + +// ErrorHandler is called when there is an error in validation. +type ErrorHandler func(c *echo.Context, err *echo.HTTPError) error + +// MultiErrorHandler is called when the OpenAPI filter returns a MultiError. +type MultiErrorHandler func(openapi3.MultiError) *echo.HTTPError + +// Options to customize request validation. +type Options struct { + ErrorHandler ErrorHandler + Options openapi3filter.Options + ParamDecoder openapi3filter.ContentParameterDecoder + UserData any + Skipper echomiddleware.Skipper + MultiErrorHandler MultiErrorHandler + SilenceServersWarning bool + DoNotValidateServers bool + Prefix string +} + +// OapiRequestValidatorWithOptions creates middleware with explicit configuration. +func OapiRequestValidatorWithOptions(spec *openapi3.T, options *Options) echo.MiddlewareFunc { + if options != nil && options.DoNotValidateServers { + spec.Servers = nil + } + + if spec.Servers != nil && (options == nil || !options.SilenceServersWarning) { + log.Println("WARN: OapiRequestValidatorWithOptions called with an OpenAPI spec that has `Servers` set. This may lead to an HTTP 400 with `no matching operation was found` when sending a valid request, as the validator performs `Host` header validation. If you're expecting `Host` header validation, you can silence this warning by setting `Options.SilenceServersWarning = true`. See https://github.com/oapi-codegen/oapi-codegen/issues/882 for more information.") + } + + router, err := gorillamux.NewRouter(spec) + if err != nil { + panic(err) + } + + skipper := getSkipperFromOptions(options) + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) error { + if skipper(c) { + return next(c) + } + + err := validateRequestFromContext(c, router, options) + if err != nil { + if options != nil && options.ErrorHandler != nil { + return options.ErrorHandler(c, err) + } + return err + } + return next(c) + } + } +} + +// validateRequestFromContext does the work of validating a request. +func validateRequestFromContext(ctx *echo.Context, router routers.Router, options *Options) *echo.HTTPError { + req := ctx.Request() + + if options != nil && options.Prefix != "" { + clone := req.Clone(req.Context()) + clone.URL.Path = strings.TrimPrefix(clone.URL.Path, options.Prefix) + req = clone + } + + route, pathParams, err := router.FindRoute(req) + if err != nil { + if errors.Is(err, routers.ErrMethodNotAllowed) { + return echo.NewHTTPError(http.StatusMethodNotAllowed, http.StatusText(http.StatusMethodNotAllowed)) + } + + switch e := err.(type) { + case *routers.RouteError: + return echo.NewHTTPError(http.StatusNotFound, e.Reason) + default: + return echo.NewHTTPError(http.StatusInternalServerError, + fmt.Sprintf("error validating route: %s", err.Error())) + } + } + + for k, v := range pathParams { + if unescaped, err := url.PathUnescape(v); err == nil { + pathParams[k] = unescaped + } + } + + validationInput := &openapi3filter.RequestValidationInput{ + Request: req, + PathParams: pathParams, + Route: route, + } + + requestContext := context.WithValue(context.Background(), EchoContextKey, ctx) //nolint:staticcheck + + if options != nil { + validationInput.Options = &options.Options + validationInput.ParamDecoder = options.ParamDecoder + requestContext = context.WithValue(requestContext, UserDataKey, options.UserData) //nolint:staticcheck + } + + err = openapi3filter.ValidateRequest(requestContext, validationInput) + if err != nil { + me := openapi3.MultiError{} + if errors.As(err, &me) { + errFunc := getMultiErrorHandlerFromOptions(options) + return errFunc(me) + } + + switch e := err.(type) { + case *openapi3filter.RequestError: + errorLines := strings.Split(e.Error(), "\n") + return echo.NewHTTPError(http.StatusBadRequest, errorLines[0]) + case *openapi3filter.SecurityRequirementsError: + for _, err := range e.Errors { + httpErr, ok := err.(*echo.HTTPError) + if ok { + return httpErr + } + } + return echo.NewHTTPError(http.StatusForbidden, e.Error()) + default: + return echo.NewHTTPError(http.StatusInternalServerError, + fmt.Sprintf("error validating request: %s", err)) + } + } + return nil +} + +// GetEchoContext gets the echo context from within requests. It returns +// nil if not found or wrong type. +func GetEchoContext(c context.Context) *echo.Context { + iface := c.Value(EchoContextKey) + if iface == nil { + return nil + } + eCtx, ok := iface.(*echo.Context) + if !ok { + return nil + } + return eCtx +} + +// GetUserData gets the user data from the context. +func GetUserData(c context.Context) any { + return c.Value(UserDataKey) +} + +func getSkipperFromOptions(options *Options) echomiddleware.Skipper { + if options == nil { + return echomiddleware.DefaultSkipper + } + + if options.Skipper == nil { + return echomiddleware.DefaultSkipper + } + + return options.Skipper +} + +func getMultiErrorHandlerFromOptions(options *Options) MultiErrorHandler { + if options == nil { + return defaultMultiErrorHandler + } + + if options.MultiErrorHandler == nil { + return defaultMultiErrorHandler + } + + return options.MultiErrorHandler +} + +func defaultMultiErrorHandler(me openapi3.MultiError) *echo.HTTPError { + return echo.NewHTTPError(http.StatusBadRequest, me.Error()) +} diff --git a/examples/petstore-expanded/echo-v5/petstore.go b/examples/petstore-expanded/echo-v5/petstore.go new file mode 100644 index 0000000000..9742cc1939 --- /dev/null +++ b/examples/petstore-expanded/echo-v5/petstore.go @@ -0,0 +1,48 @@ +// This is an example of implementing the Pet Store from the OpenAPI documentation +// found at: +// https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/petstore.yaml +// +// The code under api/petstore/ has been generated from that specification. +package main + +import ( + "flag" + "fmt" + "log" + "net" + "os" + + "github.com/labstack/echo/v5" + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/echo-v5/api" + mw "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/echo-v5/middleware" +) + +func main() { + port := flag.String("port", "8080", "Port for test HTTP server") + flag.Parse() + + swagger, err := api.GetSwagger() + if err != nil { + fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err) + os.Exit(1) + } + + // Clear out the servers array in the swagger spec, that skips validating + // that server names match. We don't know how this thing will be run. + swagger.Servers = nil + + // Create an instance of our handler which satisfies the generated interface + petStore := api.NewPetStore() + + // This is how you set up a basic Echo router + e := echo.New() + // Use our validation middleware to check all requests against the + // OpenAPI schema. + e.Use(mw.OapiRequestValidator(swagger)) + + // We now register our petStore above as the handler for the interface + api.RegisterHandlers(e, petStore) + + // And we serve HTTP until the world ends. + log.Fatal(e.Start(net.JoinHostPort("0.0.0.0", *port))) +} diff --git a/examples/petstore-expanded/echo-v5/petstore_test.go b/examples/petstore-expanded/echo-v5/petstore_test.go new file mode 100644 index 0000000000..61d0530a23 --- /dev/null +++ b/examples/petstore-expanded/echo-v5/petstore_test.go @@ -0,0 +1,152 @@ +// Copyright 2019 DeepMap, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "net/http" + "testing" + + "github.com/labstack/echo/v5" + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/echo-v5/api" + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/echo-v5/api/models" + mw "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/echo-v5/middleware" + "github.com/oapi-codegen/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPetStore(t *testing.T) { + var err error + // Here, we Initialize echo + e := echo.New() + + // Now, we create our empty pet store + store := api.NewPetStore() + + // Get the swagger description of our API + swagger, err := api.GetSwagger() + require.NoError(t, err) + + // This disables swagger server name validation. It seems to work poorly, + // and requires our test server to be in that list. + swagger.Servers = nil + + // Validate requests against the OpenAPI spec + e.Use(mw.OapiRequestValidator(swagger)) + + // We register the autogenerated boilerplate and bind our PetStore to this + // echo router. + api.RegisterHandlers(e, store) + + // At this point, we can start sending simulated Http requests, and record + // the HTTP responses to check for validity. This exercises every part of + // the stack except the well-tested HTTP system in Go, which there is no + // point for us to test. + tag := "TagOfSpot" + newPet := models.NewPet{ + Name: "Spot", + Tag: &tag, + } + result := testutil.NewRequest().Post("/pets").WithJsonBody(newPet).GoWithHTTPHandler(t, e) + // We expect 201 code on successful pet insertion + assert.Equal(t, http.StatusCreated, result.Code()) + + // We should have gotten a response from the server with the new pet. Make + // sure that its fields match. + var resultPet models.Pet + err = result.UnmarshalBodyToObject(&resultPet) + assert.NoError(t, err, "error unmarshaling response") + assert.Equal(t, newPet.Name, resultPet.Name) + assert.Equal(t, *newPet.Tag, *resultPet.Tag) + + // This is the Id of the pet we inserted. + petId := resultPet.Id + + // Test the getter function. + result = testutil.NewRequest().Get(fmt.Sprintf("/pets/%d", petId)).WithAcceptJson().GoWithHTTPHandler(t, e) + var resultPet2 models.Pet + err = result.UnmarshalBodyToObject(&resultPet2) + assert.NoError(t, err, "error getting pet") + assert.Equal(t, resultPet, resultPet2) + + // We should get a 404 on invalid ID + result = testutil.NewRequest().Get("/pets/27179095781").WithAcceptJson().GoWithHTTPHandler(t, e) + assert.Equal(t, http.StatusNotFound, result.Code()) + var petError models.Error + err = result.UnmarshalBodyToObject(&petError) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, int32(http.StatusNotFound), petError.Code) + + // Let's insert another pet for subsequent tests. + tag = "TagOfFido" + newPet = models.NewPet{ + Name: "Fido", + Tag: &tag, + } + result = testutil.NewRequest().Post("/pets").WithJsonBody(newPet).GoWithHTTPHandler(t, e) + // We expect 201 code on successful pet insertion + assert.Equal(t, http.StatusCreated, result.Code()) + // We should have gotten a response from the server with the new pet. Make + // sure that its fields match. + err = result.UnmarshalBodyToObject(&resultPet) + assert.NoError(t, err, "error unmarshaling response") + petId2 := resultPet.Id + + // Now, list all pets, we should have two + result = testutil.NewRequest().Get("/pets").WithAcceptJson().GoWithHTTPHandler(t, e) + assert.Equal(t, http.StatusOK, result.Code()) + var petList []models.Pet + err = result.UnmarshalBodyToObject(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 2, len(petList)) + + // Filter pets by tag, we should have 1 + petList = nil + result = testutil.NewRequest().Get("/pets?tags=TagOfFido").WithAcceptJson().GoWithHTTPHandler(t, e) + assert.Equal(t, http.StatusOK, result.Code()) + err = result.UnmarshalBodyToObject(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 1, len(petList)) + + // Filter pets by non existent tag, we should have 0 + petList = nil + result = testutil.NewRequest().Get("/pets?tags=NotExists").WithAcceptJson().GoWithHTTPHandler(t, e) + assert.Equal(t, http.StatusOK, result.Code()) + err = result.UnmarshalBodyToObject(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 0, len(petList)) + + // Let's delete non-existent pet + result = testutil.NewRequest().Delete("/pets/7").GoWithHTTPHandler(t, e) + assert.Equal(t, http.StatusNotFound, result.Code()) + err = result.UnmarshalBodyToObject(&petError) + assert.NoError(t, err, "error unmarshaling PetError") + assert.Equal(t, int32(http.StatusNotFound), petError.Code) + + // Now, delete both real pets + result = testutil.NewRequest().Delete(fmt.Sprintf("/pets/%d", petId)).GoWithHTTPHandler(t, e) + assert.Equal(t, http.StatusNoContent, result.Code()) + result = testutil.NewRequest().Delete(fmt.Sprintf("/pets/%d", petId2)).GoWithHTTPHandler(t, e) + assert.Equal(t, http.StatusNoContent, result.Code()) + + // Should have no pets left. + petList = nil + result = testutil.NewRequest().Get("/pets").WithAcceptJson().GoWithHTTPHandler(t, e) + assert.Equal(t, http.StatusOK, result.Code()) + err = result.UnmarshalBodyToObject(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 0, len(petList)) +} diff --git a/examples/petstore-expanded/echo-v5/tools/tools.go b/examples/petstore-expanded/echo-v5/tools/tools.go new file mode 100644 index 0000000000..8615cb4c57 --- /dev/null +++ b/examples/petstore-expanded/echo-v5/tools/tools.go @@ -0,0 +1,8 @@ +//go:build tools +// +build tools + +package tools + +import ( + _ "github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen" +) diff --git a/examples/petstore-expanded/echo/api/models.cfg.yaml b/examples/petstore-expanded/echo/api/models.cfg.yaml index c1b80a89d9..46b5e629c3 100644 --- a/examples/petstore-expanded/echo/api/models.cfg.yaml +++ b/examples/petstore-expanded/echo/api/models.cfg.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: models generate: models: true diff --git a/examples/petstore-expanded/echo/api/models/models.gen.go b/examples/petstore-expanded/echo/api/models/models.gen.go index f15f25527b..0945e02abb 100644 --- a/examples/petstore-expanded/echo/api/models/models.gen.go +++ b/examples/petstore-expanded/echo/api/models/models.gen.go @@ -1,6 +1,6 @@ // Package models provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package models // Error defines model for Error. diff --git a/examples/petstore-expanded/echo/api/petstore-server.gen.go b/examples/petstore-expanded/echo/api/petstore-server.gen.go index a71407f43e..8767c17a14 100644 --- a/examples/petstore-expanded/echo/api/petstore-server.gen.go +++ b/examples/petstore-expanded/echo/api/petstore-server.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api import ( @@ -13,10 +13,10 @@ import ( "path" "strings" - . "github.com/deepmap/oapi-codegen/examples/petstore-expanded/echo/api/models" - "github.com/deepmap/oapi-codegen/pkg/runtime" "github.com/getkin/kin-openapi/openapi3" "github.com/labstack/echo/v4" + . "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/echo/api/models" + "github.com/oapi-codegen/runtime" ) // ServerInterface represents all server handlers. @@ -60,7 +60,7 @@ func (w *ServerInterfaceWrapper) FindPets(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.FindPets(ctx, params) return err } @@ -69,7 +69,7 @@ func (w *ServerInterfaceWrapper) FindPets(ctx echo.Context) error { func (w *ServerInterfaceWrapper) AddPet(ctx echo.Context) error { var err error - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.AddPet(ctx) return err } @@ -80,12 +80,12 @@ func (w *ServerInterfaceWrapper) DeletePet(ctx echo.Context) error { // ------------- Path parameter "id" ------------- var id int64 - err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, ctx.Param("id"), &id) + err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.DeletePet(ctx, id) return err } @@ -96,12 +96,12 @@ func (w *ServerInterfaceWrapper) FindPetByID(ctx echo.Context) error { // ------------- Path parameter "id" ------------- var id int64 - err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, ctx.Param("id"), &id) + err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.FindPetByID(ctx, id) return err } @@ -179,16 +179,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -206,7 +206,7 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } @@ -220,12 +220,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/examples/petstore-expanded/echo/api/petstore.go b/examples/petstore-expanded/echo/api/petstore.go index 65229d0bbe..9934fa9921 100644 --- a/examples/petstore-expanded/echo/api/petstore.go +++ b/examples/petstore-expanded/echo/api/petstore.go @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=models.cfg.yaml ../../petstore-expanded.yaml -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=server.cfg.yaml ../../petstore-expanded.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=models.cfg.yaml ../../petstore-expanded.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=server.cfg.yaml ../../petstore-expanded.yaml package api @@ -22,8 +22,8 @@ import ( "net/http" "sync" - "github.com/deepmap/oapi-codegen/examples/petstore-expanded/echo/api/models" "github.com/labstack/echo/v4" + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/echo/api/models" ) type PetStore struct { @@ -39,7 +39,7 @@ func NewPetStore() *PetStore { } } -// This function wraps sending of an error in the Error format, and +// sendPetStoreError wraps sending of an error in the Error format, and // handling the failure to marshal that. func sendPetStoreError(ctx echo.Context, code int, message string) error { petErr := models.Error{ @@ -99,7 +99,7 @@ func (p *PetStore) AddPet(ctx echo.Context) error { pet.Name = newPet.Name pet.Tag = newPet.Tag pet.Id = p.NextId - p.NextId = p.NextId + 1 + p.NextId++ // Insert into map p.Pets[pet.Id] = pet diff --git a/examples/petstore-expanded/echo/api/server.cfg.yaml b/examples/petstore-expanded/echo/api/server.cfg.yaml index 3c93576d54..534f4d8f59 100644 --- a/examples/petstore-expanded/echo/api/server.cfg.yaml +++ b/examples/petstore-expanded/echo/api/server.cfg.yaml @@ -1,7 +1,8 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: api output: petstore-server.gen.go additional-imports: - - package: github.com/deepmap/oapi-codegen/examples/petstore-expanded/echo/api/models + - package: github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/echo/api/models alias: . generate: echo-server: true diff --git a/examples/petstore-expanded/echo/petstore.go b/examples/petstore-expanded/echo/petstore.go index af0ae8310a..7287d2b354 100644 --- a/examples/petstore-expanded/echo/petstore.go +++ b/examples/petstore-expanded/echo/petstore.go @@ -8,16 +8,16 @@ package main import ( "flag" "fmt" + "net" "os" - "github.com/deepmap/oapi-codegen/examples/petstore-expanded/echo/api" - "github.com/deepmap/oapi-codegen/pkg/middleware" "github.com/labstack/echo/v4" - echomiddleware "github.com/labstack/echo/v4/middleware" + middleware "github.com/oapi-codegen/echo-middleware" + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/echo/api" ) func main() { - var port = flag.Int("port", 8080, "Port for test HTTP server") + port := flag.String("port", "8080", "Port for test HTTP server") flag.Parse() swagger, err := api.GetSwagger() @@ -35,8 +35,6 @@ func main() { // This is how you set up a basic Echo router e := echo.New() - // Log all requests - e.Use(echomiddleware.Logger()) // Use our validation middleware to check all requests against the // OpenAPI schema. e.Use(middleware.OapiRequestValidator(swagger)) @@ -45,5 +43,5 @@ func main() { api.RegisterHandlers(e, petStore) // And we serve HTTP until the world ends. - e.Logger.Fatal(e.Start(fmt.Sprintf("0.0.0.0:%d", *port))) + e.Logger.Fatal(e.Start(net.JoinHostPort("0.0.0.0", *port))) } diff --git a/examples/petstore-expanded/echo/petstore_test.go b/examples/petstore-expanded/echo/petstore_test.go index b79d39d029..092e6238e8 100644 --- a/examples/petstore-expanded/echo/petstore_test.go +++ b/examples/petstore-expanded/echo/petstore_test.go @@ -19,12 +19,11 @@ import ( "net/http" "testing" - "github.com/deepmap/oapi-codegen/examples/petstore-expanded/echo/api" - "github.com/deepmap/oapi-codegen/examples/petstore-expanded/echo/api/models" - "github.com/deepmap/oapi-codegen/pkg/middleware" - "github.com/deepmap/oapi-codegen/pkg/testutil" "github.com/labstack/echo/v4" - echoMiddleware "github.com/labstack/echo/v4/middleware" + middleware "github.com/oapi-codegen/echo-middleware" + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/echo/api" + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/echo/api/models" + "github.com/oapi-codegen/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -48,9 +47,6 @@ func TestPetStore(t *testing.T) { // Validate requests against the OpenAPI spec e.Use(middleware.OapiRequestValidator(swagger)) - // Log requests - e.Use(echoMiddleware.Logger()) - // We register the autogenerated boilerplate and bind our PetStore to this // echo router. api.RegisterHandlers(e, store) @@ -64,7 +60,7 @@ func TestPetStore(t *testing.T) { Name: "Spot", Tag: &tag, } - result := testutil.NewRequest().Post("/pets").WithJsonBody(newPet).Go(t, e) + result := testutil.NewRequest().Post("/pets").WithJsonBody(newPet).GoWithHTTPHandler(t, e) // We expect 201 code on successful pet insertion assert.Equal(t, http.StatusCreated, result.Code()) @@ -72,7 +68,7 @@ func TestPetStore(t *testing.T) { // sure that its fields match. var resultPet models.Pet err = result.UnmarshalBodyToObject(&resultPet) - assert.NoError(t, err, "error unmarshalling response") + assert.NoError(t, err, "error unmarshaling response") assert.Equal(t, newPet.Name, resultPet.Name) assert.Equal(t, *newPet.Tag, *resultPet.Tag) @@ -80,14 +76,14 @@ func TestPetStore(t *testing.T) { petId := resultPet.Id // Test the getter function. - result = testutil.NewRequest().Get(fmt.Sprintf("/pets/%d", petId)).WithAcceptJson().Go(t, e) + result = testutil.NewRequest().Get(fmt.Sprintf("/pets/%d", petId)).WithAcceptJson().GoWithHTTPHandler(t, e) var resultPet2 models.Pet err = result.UnmarshalBodyToObject(&resultPet2) assert.NoError(t, err, "error getting pet") assert.Equal(t, resultPet, resultPet2) // We should get a 404 on invalid ID - result = testutil.NewRequest().Get("/pets/27179095781").WithAcceptJson().Go(t, e) + result = testutil.NewRequest().Get("/pets/27179095781").WithAcceptJson().GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusNotFound, result.Code()) var petError models.Error err = result.UnmarshalBodyToObject(&petError) @@ -100,17 +96,17 @@ func TestPetStore(t *testing.T) { Name: "Fido", Tag: &tag, } - result = testutil.NewRequest().Post("/pets").WithJsonBody(newPet).Go(t, e) + result = testutil.NewRequest().Post("/pets").WithJsonBody(newPet).GoWithHTTPHandler(t, e) // We expect 201 code on successful pet insertion assert.Equal(t, http.StatusCreated, result.Code()) // We should have gotten a response from the server with the new pet. Make // sure that its fields match. err = result.UnmarshalBodyToObject(&resultPet) - assert.NoError(t, err, "error unmarshalling response") + assert.NoError(t, err, "error unmarshaling response") petId2 := resultPet.Id // Now, list all pets, we should have two - result = testutil.NewRequest().Get("/pets").WithAcceptJson().Go(t, e) + result = testutil.NewRequest().Get("/pets").WithAcceptJson().GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) var petList []models.Pet err = result.UnmarshalBodyToObject(&petList) @@ -119,7 +115,7 @@ func TestPetStore(t *testing.T) { // Filter pets by tag, we should have 1 petList = nil - result = testutil.NewRequest().Get("/pets?tags=TagOfFido").WithAcceptJson().Go(t, e) + result = testutil.NewRequest().Get("/pets?tags=TagOfFido").WithAcceptJson().GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) err = result.UnmarshalBodyToObject(&petList) assert.NoError(t, err, "error getting response", err) @@ -127,28 +123,28 @@ func TestPetStore(t *testing.T) { // Filter pets by non existent tag, we should have 0 petList = nil - result = testutil.NewRequest().Get("/pets?tags=NotExists").WithAcceptJson().Go(t, e) + result = testutil.NewRequest().Get("/pets?tags=NotExists").WithAcceptJson().GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) err = result.UnmarshalBodyToObject(&petList) assert.NoError(t, err, "error getting response", err) assert.Equal(t, 0, len(petList)) // Let's delete non-existent pet - result = testutil.NewRequest().Delete("/pets/7").Go(t, e) + result = testutil.NewRequest().Delete("/pets/7").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusNotFound, result.Code()) err = result.UnmarshalBodyToObject(&petError) - assert.NoError(t, err, "error unmarshalling PetError") + assert.NoError(t, err, "error unmarshaling PetError") assert.Equal(t, int32(http.StatusNotFound), petError.Code) // Now, delete both real pets - result = testutil.NewRequest().Delete(fmt.Sprintf("/pets/%d", petId)).Go(t, e) + result = testutil.NewRequest().Delete(fmt.Sprintf("/pets/%d", petId)).GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusNoContent, result.Code()) - result = testutil.NewRequest().Delete(fmt.Sprintf("/pets/%d", petId2)).Go(t, e) + result = testutil.NewRequest().Delete(fmt.Sprintf("/pets/%d", petId2)).GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusNoContent, result.Code()) // Should have no pets left. petList = nil - result = testutil.NewRequest().Get("/pets").WithAcceptJson().Go(t, e) + result = testutil.NewRequest().Get("/pets").WithAcceptJson().GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) err = result.UnmarshalBodyToObject(&petList) assert.NoError(t, err, "error getting response", err) diff --git a/examples/petstore-expanded/echo/pkg_codegen_petstore_test.go b/examples/petstore-expanded/echo/pkg_codegen_petstore_test.go new file mode 100644 index 0000000000..de651ec54f --- /dev/null +++ b/examples/petstore-expanded/echo/pkg_codegen_petstore_test.go @@ -0,0 +1,201 @@ +package main + +import ( + "bytes" + _ "embed" + "go/format" + "io" + "net/http" + "net/http/httptest" + "testing" + + examplePetstoreClient "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded" + examplePetstore "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/echo/api" + "github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen" + "github.com/stretchr/testify/assert" + "golang.org/x/lint" +) + +func checkLint(t *testing.T, filename string, code []byte) { + linter := new(lint.Linter) + problems, err := linter.Lint(filename, code) + assert.NoError(t, err) + assert.Len(t, problems, 0) +} + +func TestExamplePetStoreCodeGeneration(t *testing.T) { + + // Input vars for code generation: + packageName := "api" + opts := codegen.Configuration{ + PackageName: packageName, + Generate: codegen.GenerateOptions{ + EchoServer: true, + Client: true, + Models: true, + EmbeddedSpec: true, + }, + } + + // Get a spec from the example PetStore definition: + swagger, err := examplePetstore.GetSwagger() + assert.NoError(t, err) + + // Run our code generation: + code, err := codegen.Generate(swagger, opts) + assert.NoError(t, err) + assert.NotEmpty(t, code) + + // Check that we have valid (formattable) code: + _, err = format.Source([]byte(code)) + assert.NoError(t, err) + + // Check that we have a package: + assert.Contains(t, code, "package api") + + // Check that the client method signatures return response structs: + assert.Contains(t, code, "func (c *Client) FindPetByID(ctx context.Context, id int64, reqEditors ...RequestEditorFn) (*http.Response, error) {") + + // Check that the property comments were generated + assert.Contains(t, code, "// Id Unique id of the pet") + + // Check that the summary comment contains newlines + assert.Contains(t, code, `// Deletes a pet by ID + // (DELETE /pets/{id}) +`) + + // Make sure the generated code is valid: + checkLint(t, "test.gen.go", []byte(code)) +} + +func TestExamplePetStoreCodeGenerationWithUserTemplates(t *testing.T) { + + userTemplates := map[string]string{"typedef.tmpl": "//blah\n//blah"} + + // Input vars for code generation: + packageName := "api" + opts := codegen.Configuration{ + PackageName: packageName, + Generate: codegen.GenerateOptions{ + Models: true, + }, + OutputOptions: codegen.OutputOptions{ + UserTemplates: userTemplates, + }, + } + + // Get a spec from the example PetStore definition: + swagger, err := examplePetstore.GetSwagger() + assert.NoError(t, err) + + // Run our code generation: + code, err := codegen.Generate(swagger, opts) + assert.NoError(t, err) + assert.NotEmpty(t, code) + + // Check that we have valid (formattable) code: + _, err = format.Source([]byte(code)) + assert.NoError(t, err) + + // Check that we have a package: + assert.Contains(t, code, "package api") + + // Check that the built-in template has been overridden + assert.Contains(t, code, "//blah") +} + +func TestExamplePetStoreCodeGenerationWithFileUserTemplates(t *testing.T) { + + userTemplates := map[string]string{"typedef.tmpl": "../../../pkg/codegen/templates/typedef.tmpl"} + + // Input vars for code generation: + packageName := "api" + opts := codegen.Configuration{ + PackageName: packageName, + Generate: codegen.GenerateOptions{ + Models: true, + }, + OutputOptions: codegen.OutputOptions{ + UserTemplates: userTemplates, + }, + } + + // Get a spec from the example PetStore definition: + swagger, err := examplePetstore.GetSwagger() + assert.NoError(t, err) + + // Run our code generation: + code, err := codegen.Generate(swagger, opts) + assert.NoError(t, err) + assert.NotEmpty(t, code) + + // Check that we have valid (formattable) code: + _, err = format.Source([]byte(code)) + assert.NoError(t, err) + + // Check that we have a package: + assert.Contains(t, code, "package api") + + // Check that the built-in template has been overridden + assert.Contains(t, code, "// Package api provides primitives to interact with the openapi") +} + +func TestExamplePetStoreCodeGenerationWithHTTPUserTemplates(t *testing.T) { + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, writeErr := w.Write([]byte("//blah")) + assert.NoError(t, writeErr) + })) + + userTemplates := map[string]string{"typedef.tmpl": srv.URL} + + // Input vars for code generation: + packageName := "api" + opts := codegen.Configuration{ + PackageName: packageName, + Generate: codegen.GenerateOptions{ + Models: true, + }, + OutputOptions: codegen.OutputOptions{ + UserTemplates: userTemplates, + }, + } + + // Get a spec from the example PetStore definition: + swagger, err := examplePetstore.GetSwagger() + assert.NoError(t, err) + + // Run our code generation: + code, err := codegen.Generate(swagger, opts) + assert.NoError(t, err) + assert.NotEmpty(t, code) + + // Check that we have valid (formattable) code: + _, err = format.Source([]byte(code)) + assert.NoError(t, err) + + // Check that we have a package: + assert.Contains(t, code, "package api") + + // Check that the built-in template has been overridden + assert.Contains(t, code, "//blah") +} +func TestExamplePetStoreParseFunction(t *testing.T) { + + bodyBytes := []byte(`{"id": 5, "name": "testpet", "tag": "cat"}`) + + cannedResponse := &http.Response{ + StatusCode: 200, + Body: io.NopCloser(bytes.NewReader(bodyBytes)), + Header: http.Header{}, + } + cannedResponse.Header.Add("Content-type", "application/json") + + findPetByIDResponse, err := examplePetstoreClient.ParseFindPetByIDResponse(cannedResponse) + assert.NoError(t, err) + assert.NotNil(t, findPetByIDResponse.JSON200) + assert.Equal(t, int64(5), findPetByIDResponse.JSON200.Id) + assert.Equal(t, "testpet", findPetByIDResponse.JSON200.Name) + assert.NotNil(t, findPetByIDResponse.JSON200.Tag) + assert.Equal(t, "cat", *findPetByIDResponse.JSON200.Tag) +} diff --git a/examples/petstore-expanded/fiber/api/petstore-server.gen.go b/examples/petstore-expanded/fiber/api/petstore-server.gen.go new file mode 100644 index 0000000000..a5c583ef06 --- /dev/null +++ b/examples/petstore-expanded/fiber/api/petstore-server.gen.go @@ -0,0 +1,246 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/gofiber/fiber/v2" + "github.com/oapi-codegen/runtime" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Returns all pets + // (GET /pets) + FindPets(c *fiber.Ctx, params FindPetsParams) error + // Creates a new pet + // (POST /pets) + AddPet(c *fiber.Ctx) error + // Deletes a pet by ID + // (DELETE /pets/{id}) + DeletePet(c *fiber.Ctx, id int64) error + // Returns a pet by ID + // (GET /pets/{id}) + FindPetByID(c *fiber.Ctx, id int64) error +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +type MiddlewareFunc fiber.Handler + +// FindPets operation middleware +func (siw *ServerInterfaceWrapper) FindPets(c *fiber.Ctx) error { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params FindPetsParams + + var query url.Values + query, err = url.ParseQuery(string(c.Request().URI().QueryString())) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for query string: %w", err).Error()) + } + + // ------------- Optional query parameter "tags" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "tags", query, ¶ms.Tags, runtime.BindQueryParameterOptions{Type: "array", Format: ""}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter tags: %w", err).Error()) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "limit", query, ¶ms.Limit, runtime.BindQueryParameterOptions{Type: "integer", Format: "int32"}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter limit: %w", err).Error()) + } + + return siw.Handler.FindPets(c, params) +} + +// AddPet operation middleware +func (siw *ServerInterfaceWrapper) AddPet(c *fiber.Ctx) error { + + return siw.Handler.AddPet(c) +} + +// DeletePet operation middleware +func (siw *ServerInterfaceWrapper) DeletePet(c *fiber.Ctx) error { + + var err error + + // ------------- Path parameter "id" ------------- + var id int64 + + err = runtime.BindStyledParameterWithOptions("simple", "id", c.Params("id"), &id, runtime.BindStyledParameterOptions{Explode: false, Required: true, Type: "integer", Format: "int64"}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter id: %w", err).Error()) + } + + return siw.Handler.DeletePet(c, id) +} + +// FindPetByID operation middleware +func (siw *ServerInterfaceWrapper) FindPetByID(c *fiber.Ctx) error { + + var err error + + // ------------- Path parameter "id" ------------- + var id int64 + + err = runtime.BindStyledParameterWithOptions("simple", "id", c.Params("id"), &id, runtime.BindStyledParameterOptions{Explode: false, Required: true, Type: "integer", Format: "int64"}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter id: %w", err).Error()) + } + + return siw.Handler.FindPetByID(c, id) +} + +// FiberServerOptions provides options for the Fiber server. +type FiberServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router fiber.Router, si ServerInterface) { + RegisterHandlersWithOptions(router, si, FiberServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router fiber.Router, si ServerInterface, options FiberServerOptions) { + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + for _, m := range options.Middlewares { + router.Use(fiber.Handler(m)) + } + + router.Get(options.BaseURL+"/pets", wrapper.FindPets) + + router.Post(options.BaseURL+"/pets", wrapper.AddPet) + + router.Delete(options.BaseURL+"/pets/:id", wrapper.DeletePet) + + router.Get(options.BaseURL+"/pets/:id", wrapper.FindPetByID) + +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/+RXW48budH9KwV+32OnNbEXedBTvB4vICBrT+LdvKznoYZdkmrBSw9Z1FgY6L8HRbZu", + "I3k2QYIgQV506WY1T51zqlj9bGz0YwwUJJv5s8l2TR7rzw8pxaQ/xhRHSsJUL9s4kH4PlG3iUTgGM2+L", + "od7rzDImj2LmhoO8fWM6I9uR2l9aUTK7znjKGVfffND+9iE0S+KwMrtdZxI9Fk40mPkvZtpwv/x+15mP", + "9HRHcok7oL+y3Uf0BHEJsiYYSS437Izg6jLup+34etwLoHV3hTdhQ+c+Lc38l2fz/4mWZm7+b3YUYjap", + "MJty2XUvk+HhEtLPgR8LAQ/nuE7F+MN3V8R4gZQHc7+73+llDsvYJA+CtuImj+zM3ODIQuj/mJ9wtaLU", + "czTdRLH53K7Bu7sF/EToTWdK0qC1yJjns9lJ0K57kcU7yOhHRzVa1ihQMmVAzSZLTASYAQPQ17ZMIgzk", + "Y8iSUAiWhFISZeBQOfg0UtAnve1vII9keckW61adcWwpZDqaw7wb0a4J3vQ3F5ifnp56rLf7mFazKTbP", + "/rR4/+Hj5w+/e9Pf9GvxrjqGks+flp8pbdjS1cRndc1M5WBxp6zdTXmazmwo5cbK7/ub/kYfHUcKOLKZ", + "m7f1UmdGlHX1xEwZ0h+rZrFzXv9CUlLIgM5VKmGZoq8U5W0W8o1r/V8yJVgry9ZSziDxS/iIHjINYGMY", + "2FOQ4oGy9PAjkqWAGYT8GBNkXLEIZ8g4MoUOAllI6xhsyZDJnyxgAfQkPbyjQBgABVYJNzwgYFkV6gAt", + "MNriuIb28L4kfGApCeLAEVxM5DuIKWAioBUJkKMJXSDbgS0pl6wl4chKyT3cFs7gGaSkkXMHY3EbDph0", + "L0pRk+5AOFgeShDYYOKS4deSJfawCLBGC2sFgTkTjA6FEAa2UrzSsWhFpbngwCNny2EFGESzOebueFUc", + "HjIf15hIEu5J1PXgo6MsTMB+pDSwMvVX3qBvCaHjx4IeBkZlJmGGR81tQ44FQgwgMUlMSgkvKQyH3Xu4", + "S0iZgihMCuyPAEoKCJvoiowosKFAARVwI1c/PJakz1iE45OXlCbWl2jZcT7bpO6gH91RXws5DuhIhR06", + "5dFSQtHE9LuHzyWPFAZWlh2qeYboYurUgZmsqJtrltUqmnUHG1qzLQ5BW1saigfHD5RiDz/G9MBAhbOP", + "w6kMersa26HlwNh/CV/CZxqqEiXDktR8Lj7EVAMoHh2TiqTie9Da8FgfOJHP2XVA5axamuTgivpQ3dnD", + "3RozOdcKY6Q0hVeaq7wksMRi+aE0wnG/j647jd+Qm6TjDaWE3fnWWifAQ3coxMAP6x5+FhjJOQpCWU+O", + "MeZCWkn7IupBqcB9FWjR7bncP2mfVmWyq0AOtgglWJDEWerBtGFB6uGHki0BSe0GQ+FDFWinyJYcJa5w", + "mn/3AV7dUrCaxxafMYDHlaZMblKrhz+XFuqjU92aelSad45QukPzASxWi6StnOzZ0p7MMTWZQzWqWVRg", + "4NAdoUyFGzjzHnBWDJalDKxQc0YosvfZJGTb6Yy0ul8Pd6fCVOYmjGMi4eJPOlczTelO/K2tt/+iZ5wO", + "DfW8Wwxmbn7gMOj5Uo+NpARQynUKOT8sBFfa92HJTijBw9boMGDm5rFQ2h5Pel1numlorHOJkK9n0OUU", + "1S5gSrjV/1m29djT8aQOOOcIPH5lr228+AdKOtEkysVJhZXqWfYNTI49yxmo3xxHd/c6AuVRW0tF/+bm", + "Zj/3UGjz2ji6aXKY/ZoV4vO1tF8b5tok94KI3cUANJLAHkwbj5ZYnPxDeF6D0cb6KxuXQF9Hba3ag9ua", + "zuTiPabtlQFCsY0xXxk13idCqTNboCddux/G6lyjZ3DDrkt0nnMuPtFwYdZ3g3rVtOmUsnwfh+2/jIX9", + "ZH1Jwx2JegyHQb8OsM3plCyp0O6f9MxvWuW/xxoXgtf7dR6dPfOwaxZxJFdewNp1jc0cVq6+tcADapuN", + "zTWLW8hFc7rikdsa3Wzyakdb3GoPGZu2E5apf+gAfWwfPFwo/a1ecv1t6rKXfHeZtQJpKIb/JCFvD2JU", + "FbawuFV4r79QnCt20HFx+63j5/ttvff367Ukset/m1z/s2X8QtGmfl1CabOX6fyteP9S3p+82err6e5+", + "97cAAAD//ykDnxlaEgAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/examples/petstore-expanded/fiber/api/petstore-types.gen.go b/examples/petstore-expanded/fiber/api/petstore-types.gen.go new file mode 100644 index 0000000000..0e4954f349 --- /dev/null +++ b/examples/petstore-expanded/fiber/api/petstore-types.gen.go @@ -0,0 +1,46 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +// Error defines model for Error. +type Error struct { + // Code Error code + Code int32 `json:"code"` + + // Message Error message + Message string `json:"message"` +} + +// NewPet defines model for NewPet. +type NewPet struct { + // Name Name of the pet + Name string `json:"name"` + + // Tag Type of the pet + Tag *string `json:"tag,omitempty"` +} + +// Pet defines model for Pet. +type Pet struct { + // Id Unique id of the pet + Id int64 `json:"id"` + + // Name Name of the pet + Name string `json:"name"` + + // Tag Type of the pet + Tag *string `json:"tag,omitempty"` +} + +// FindPetsParams defines parameters for FindPets. +type FindPetsParams struct { + // Tags tags to filter by + Tags *[]string `form:"tags,omitempty" json:"tags,omitempty"` + + // Limit maximum number of results to return + Limit *int32 `form:"limit,omitempty" json:"limit,omitempty"` +} + +// AddPetJSONRequestBody defines body for AddPet for application/json ContentType. +type AddPetJSONRequestBody = NewPet diff --git a/examples/petstore-expanded/fiber/api/petstore.go b/examples/petstore-expanded/fiber/api/petstore.go new file mode 100644 index 0000000000..38eec12d65 --- /dev/null +++ b/examples/petstore-expanded/fiber/api/petstore.go @@ -0,0 +1,132 @@ +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=types.cfg.yaml ../../petstore-expanded.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=server.cfg.yaml ../../petstore-expanded.yaml + +package api + +import ( + "fmt" + "net/http" + "sync" + + "github.com/gofiber/fiber/v2" +) + +type PetStore struct { + Pets map[int64]Pet + NextId int64 + Lock sync.Mutex +} + +// Make sure we conform to ServerInterface + +var _ ServerInterface = (*PetStore)(nil) + +func NewPetStore() *PetStore { + + return &PetStore{ + Pets: make(map[int64]Pet), + NextId: 1000, + } +} + +// This function wraps sending of an error in the Error format, and +// handling the failure to marshal that. +func sendPetStoreError(c *fiber.Ctx, code int, message string) error { + + petErr := Error{ + Code: int32(code), + Message: message, + } + + return c.Status(code).JSON(petErr) +} + +// FindPets implements all the handlers in the ServerInterface +func (p *PetStore) FindPets(c *fiber.Ctx, params FindPetsParams) error { + + p.Lock.Lock() + defer p.Lock.Unlock() + + var result []Pet + + for _, pet := range p.Pets { + if params.Tags != nil { + // If we have tags, filter pets by tag + for _, t := range *params.Tags { + if pet.Tag != nil && (*pet.Tag == t) { + result = append(result, pet) + } + } + } else { + // Add all pets if we're not filtering + result = append(result, pet) + } + + if params.Limit != nil { + l := int(*params.Limit) + if len(result) >= l { + // We're at the limit + break + } + } + } + + return c.Status(http.StatusOK).JSON(result) +} + +func (p *PetStore) AddPet(c *fiber.Ctx) error { + + // We expect a NewPet object in the request body. + var newPet NewPet + + if err := c.BodyParser(&newPet); err != nil { + return sendPetStoreError(c, http.StatusBadRequest, "Invalid format for NewPet") + } + + // We now have a pet, let's add it to our "database". + + // We're always asynchronous, so lock unsafe operations below + p.Lock.Lock() + defer p.Lock.Unlock() + + // We handle pets, not NewPets, which have an additional ID field + var pet Pet + pet.Name = newPet.Name + pet.Tag = newPet.Tag + pet.Id = p.NextId + p.NextId++ + + // Insert into map + p.Pets[pet.Id] = pet + + // Now, we have to return the NewPet + return c.Status(http.StatusCreated).JSON(pet) +} + +func (p *PetStore) FindPetByID(c *fiber.Ctx, id int64) error { + + p.Lock.Lock() + defer p.Lock.Unlock() + + pet, found := p.Pets[id] + if !found { + return sendPetStoreError(c, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", id)) + } + + return c.Status(http.StatusOK).JSON(pet) +} + +func (p *PetStore) DeletePet(c *fiber.Ctx, id int64) error { + + p.Lock.Lock() + defer p.Lock.Unlock() + + _, found := p.Pets[id] + if !found { + return sendPetStoreError(c, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", id)) + } + delete(p.Pets, id) + + c.Status(http.StatusNoContent) + return nil +} diff --git a/examples/petstore-expanded/fiber/api/server.cfg.yaml b/examples/petstore-expanded/fiber/api/server.cfg.yaml new file mode 100644 index 0000000000..6384086293 --- /dev/null +++ b/examples/petstore-expanded/fiber/api/server.cfg.yaml @@ -0,0 +1,6 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +generate: + fiber-server: true + embedded-spec: true +output: petstore-server.gen.go diff --git a/examples/petstore-expanded/fiber/api/types.cfg.yaml b/examples/petstore-expanded/fiber/api/types.cfg.yaml new file mode 100644 index 0000000000..cf5128b726 --- /dev/null +++ b/examples/petstore-expanded/fiber/api/types.cfg.yaml @@ -0,0 +1,5 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +generate: + models: true +output: petstore-types.gen.go diff --git a/examples/petstore-expanded/fiber/petstore.go b/examples/petstore-expanded/fiber/petstore.go new file mode 100644 index 0000000000..6332ff8866 --- /dev/null +++ b/examples/petstore-expanded/fiber/petstore.go @@ -0,0 +1,56 @@ +// This is an example of implementing the Pet Store from the OpenAPI documentation +// found at: +// https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/petstore.yaml + +package main + +import ( + "flag" + "fmt" + "log" + "net" + "os" + + "github.com/gofiber/fiber/v2" + + middleware "github.com/oapi-codegen/fiber-middleware" + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/fiber/api" +) + +func main() { + port := flag.String("port", "8080", "Port for test HTTP server") + flag.Parse() + + // Create an instance of our handler which satisfies the generated interface + petStore := api.NewPetStore() + + s := NewFiberPetServer(petStore) + + // And we serve HTTP until the world ends. + log.Fatal(s.Listen(net.JoinHostPort("0.0.0.0", *port))) +} + +func NewFiberPetServer(petStore *api.PetStore) *fiber.App { + + swagger, err := api.GetSwagger() + if err != nil { + fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err) + os.Exit(1) + } + + // Clear out the servers array in the swagger spec, that skips validating + // that server names match. We don't know how this thing will be run. + swagger.Servers = nil + + // This is how you set up a basic fiber router + app := fiber.New() + + // Use our validation middleware to check all requests against the + // OpenAPI schema. + app.Use(middleware.OapiRequestValidator(swagger)) + + // We now register our petStore above as the handler for the interface + api.RegisterHandlers(app, petStore) + + return app +} diff --git a/examples/petstore-expanded/fiber/petstore_test.go b/examples/petstore-expanded/fiber/petstore_test.go new file mode 100644 index 0000000000..dac1d58bf0 --- /dev/null +++ b/examples/petstore-expanded/fiber/petstore_test.go @@ -0,0 +1,181 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/gofiber/fiber/v2" + "github.com/stretchr/testify/assert" + + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/fiber/api" +) + +func doGet(t *testing.T, app *fiber.App, rawURL string) (*http.Response, error) { + + u, err := url.Parse(rawURL) + if err != nil { + t.Fatalf("Invalid url: %s", rawURL) + } + + req := httptest.NewRequest("GET", u.RequestURI(), nil) + req.Header.Add("Accept", "application/json") + req.Host = u.Host + + return app.Test(req) +} + +func doPost(t *testing.T, app *fiber.App, rawURL string, jsonBody interface{}) (*http.Response, error) { + u, err := url.Parse(rawURL) + if err != nil { + t.Fatalf("Invalid url: %s", rawURL) + } + + buf, err := json.Marshal(jsonBody) + if err != nil { + return nil, err + } + req := httptest.NewRequest("POST", u.RequestURI(), bytes.NewReader(buf)) + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json") + req.Host = u.Host + return app.Test(req) +} + +func doDelete(t *testing.T, app *fiber.App, rawURL string) (*http.Response, error) { + u, err := url.Parse(rawURL) + if err != nil { + t.Fatalf("Invalid url: %s", rawURL) + } + + req := httptest.NewRequest("DELETE", u.RequestURI(), nil) + req.Header.Add("Accept", "application/json") + req.Host = u.Host + return app.Test(req) +} + +func TestPetStore(t *testing.T) { + var err error + store := api.NewPetStore() + fiberPetServer := NewFiberPetServer(store) + + t.Run("Add pet", func(t *testing.T) { + tag := "TagOfSpot" + newPet := api.NewPet{ + Name: "Spot", + Tag: &tag, + } + + rr, _ := doPost(t, fiberPetServer, "/pets", newPet) + assert.Equal(t, http.StatusCreated, rr.StatusCode) + + var resultPet api.Pet + err = json.NewDecoder(rr.Body).Decode(&resultPet) + assert.NoError(t, err, "error unmarshaling response") + assert.Equal(t, newPet.Name, resultPet.Name) + assert.Equal(t, *newPet.Tag, *resultPet.Tag) + }) + + t.Run("Find pet by ID", func(t *testing.T) { + pet := api.Pet{ + Id: 100, + } + + store.Pets[pet.Id] = pet + rr, _ := doGet(t, fiberPetServer, fmt.Sprintf("/pets/%d", pet.Id)) + assert.Equal(t, http.StatusOK, rr.StatusCode) + + var resultPet api.Pet + err = json.NewDecoder(rr.Body).Decode(&resultPet) + assert.NoError(t, err, "error getting pet") + assert.Equal(t, pet, resultPet) + }) + + t.Run("Pet not found", func(t *testing.T) { + rr, _ := doGet(t, fiberPetServer, "/pets/27179095781") + assert.Equal(t, http.StatusNotFound, rr.StatusCode) + + var petError api.Error + err = json.NewDecoder(rr.Body).Decode(&petError) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, int32(http.StatusNotFound), petError.Code) + }) + + t.Run("List all pets", func(t *testing.T) { + store.Pets = map[int64]api.Pet{1: {}, 2: {}} + + // Now, list all pets, we should have two + rr, _ := doGet(t, fiberPetServer, "/pets") + assert.Equal(t, http.StatusOK, rr.StatusCode) + + var petList []api.Pet + err = json.NewDecoder(rr.Body).Decode(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 2, len(petList)) + }) + + t.Run("Filter pets by tag", func(t *testing.T) { + tag := "TagOfFido" + + store.Pets = map[int64]api.Pet{ + 1: { + Tag: &tag, + }, + 2: {}, + } + + // Filter pets by tag, we should have 1 + rr, _ := doGet(t, fiberPetServer, "/pets?tags=TagOfFido") + assert.Equal(t, http.StatusOK, rr.StatusCode) + + var petList []api.Pet + err = json.NewDecoder(rr.Body).Decode(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 1, len(petList)) + }) + + t.Run("Filter pets by tag", func(t *testing.T) { + store.Pets = map[int64]api.Pet{1: {}, 2: {}} + + // Filter pets by non existent tag, we should have 0 + rr, _ := doGet(t, fiberPetServer, "/pets?tags=NotExists") + assert.Equal(t, http.StatusOK, rr.StatusCode) + + var petList []api.Pet + err = json.NewDecoder(rr.Body).Decode(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 0, len(petList)) + }) + + t.Run("Delete pets", func(t *testing.T) { + store.Pets = map[int64]api.Pet{1: {}, 2: {}} + + // Let's delete non-existent pet + rr, _ := doDelete(t, fiberPetServer, "/pets/7") + assert.Equal(t, http.StatusNotFound, rr.StatusCode) + + var petError api.Error + err = json.NewDecoder(rr.Body).Decode(&petError) + assert.NoError(t, err, "error unmarshaling PetError") + assert.Equal(t, int32(http.StatusNotFound), petError.Code) + + // Now, delete both real pets + rr, _ = doDelete(t, fiberPetServer, "/pets/1") + assert.Equal(t, http.StatusNoContent, rr.StatusCode) + + rr, _ = doDelete(t, fiberPetServer, "/pets/2") + assert.Equal(t, http.StatusNoContent, rr.StatusCode) + + // Should have no pets left. + var petList []api.Pet + rr, _ = doGet(t, fiberPetServer, "/pets") + assert.Equal(t, http.StatusOK, rr.StatusCode) + err = json.NewDecoder(rr.Body).Decode(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 0, len(petList)) + }) +} diff --git a/examples/petstore-expanded/gin/api/petstore-server.gen.go b/examples/petstore-expanded/gin/api/petstore-server.gen.go index 3b0368db57..02499fcc18 100644 --- a/examples/petstore-expanded/gin/api/petstore-server.gen.go +++ b/examples/petstore-expanded/gin/api/petstore-server.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api import ( @@ -13,9 +13,9 @@ import ( "path" "strings" - "github.com/deepmap/oapi-codegen/pkg/runtime" "github.com/getkin/kin-openapi/openapi3" "github.com/gin-gonic/gin" + "github.com/oapi-codegen/runtime" ) // ServerInterface represents all server handlers. @@ -55,7 +55,7 @@ func (siw *ServerInterfaceWrapper) FindPets(c *gin.Context) { err = runtime.BindQueryParameter("form", true, false, "tags", c.Request.URL.Query(), ¶ms.Tags) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter tags: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter tags: %w", err), http.StatusBadRequest) return } @@ -63,7 +63,7 @@ func (siw *ServerInterfaceWrapper) FindPets(c *gin.Context) { err = runtime.BindQueryParameter("form", true, false, "limit", c.Request.URL.Query(), ¶ms.Limit) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter limit: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter limit: %w", err), http.StatusBadRequest) return } @@ -98,9 +98,9 @@ func (siw *ServerInterfaceWrapper) DeletePet(c *gin.Context) { // ------------- Path parameter "id" ------------- var id int64 - err = runtime.BindStyledParameter("simple", false, "id", c.Param("id"), &id) + err = runtime.BindStyledParameterWithOptions("simple", "id", c.Param("id"), &id, runtime.BindStyledParameterOptions{Explode: false, Required: true}) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter id: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter id: %w", err), http.StatusBadRequest) return } @@ -122,9 +122,9 @@ func (siw *ServerInterfaceWrapper) FindPetByID(c *gin.Context) { // ------------- Path parameter "id" ------------- var id int64 - err = runtime.BindStyledParameter("simple", false, "id", c.Param("id"), &id) + err = runtime.BindStyledParameterWithOptions("simple", "id", c.Param("id"), &id, runtime.BindStyledParameterOptions{Explode: false, Required: true}) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter id: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter id: %w", err), http.StatusBadRequest) return } @@ -209,16 +209,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -236,7 +236,7 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } @@ -250,12 +250,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/examples/petstore-expanded/gin/api/petstore-types.gen.go b/examples/petstore-expanded/gin/api/petstore-types.gen.go index 16bbe200cf..0e4954f349 100644 --- a/examples/petstore-expanded/gin/api/petstore-types.gen.go +++ b/examples/petstore-expanded/gin/api/petstore-types.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api // Error defines model for Error. diff --git a/examples/petstore-expanded/gin/api/petstore.go b/examples/petstore-expanded/gin/api/petstore.go index 2654d37986..094b4ef9eb 100644 --- a/examples/petstore-expanded/gin/api/petstore.go +++ b/examples/petstore-expanded/gin/api/petstore.go @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=types.cfg.yaml ../../petstore-expanded.yaml -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=server.cfg.yaml ../../petstore-expanded.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=types.cfg.yaml ../../petstore-expanded.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=server.cfg.yaml ../../petstore-expanded.yaml package api @@ -38,7 +38,7 @@ func NewPetStore() *PetStore { } } -// This function wraps sending of an error in the Error format, and +// sendPetStoreError wraps sending of an error in the Error format, and // handling the failure to marshal that. func sendPetStoreError(c *gin.Context, code int, message string) { petErr := Error{ @@ -98,14 +98,13 @@ func (p *PetStore) AddPet(c *gin.Context) { pet.Name = newPet.Name pet.Tag = newPet.Tag pet.Id = p.NextId - p.NextId = p.NextId + 1 + p.NextId++ // Insert into map p.Pets[pet.Id] = pet // Now, we have to return the NewPet c.JSON(http.StatusCreated, pet) - return } func (p *PetStore) FindPetByID(c *gin.Context, petId int64) { diff --git a/examples/petstore-expanded/gin/api/server.cfg.yaml b/examples/petstore-expanded/gin/api/server.cfg.yaml index 61944bcef0..fe54882eb9 100644 --- a/examples/petstore-expanded/gin/api/server.cfg.yaml +++ b/examples/petstore-expanded/gin/api/server.cfg.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: api generate: gin-server: true diff --git a/examples/petstore-expanded/gin/api/types.cfg.yaml b/examples/petstore-expanded/gin/api/types.cfg.yaml index 9ac30e11e9..cf5128b726 100644 --- a/examples/petstore-expanded/gin/api/types.cfg.yaml +++ b/examples/petstore-expanded/gin/api/types.cfg.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: api generate: models: true diff --git a/examples/petstore-expanded/gin/petstore.go b/examples/petstore-expanded/gin/petstore.go index a5b40a2f4c..8b0717a4c8 100644 --- a/examples/petstore-expanded/gin/petstore.go +++ b/examples/petstore-expanded/gin/petstore.go @@ -8,16 +8,17 @@ import ( "flag" "fmt" "log" + "net" "net/http" "os" "github.com/gin-gonic/gin" - "github.com/deepmap/oapi-codegen/examples/petstore-expanded/gin/api" - middleware "github.com/deepmap/oapi-codegen/pkg/gin-middleware" + middleware "github.com/oapi-codegen/gin-middleware" + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/gin/api" ) -func NewGinPetServer(petStore *api.PetStore, port int) *http.Server { +func NewGinPetServer(petStore *api.PetStore, port string) *http.Server { swagger, err := api.GetSwagger() if err != nil { fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err) @@ -40,13 +41,13 @@ func NewGinPetServer(petStore *api.PetStore, port int) *http.Server { s := &http.Server{ Handler: r, - Addr: fmt.Sprintf("0.0.0.0:%d", port), + Addr: net.JoinHostPort("0.0.0.0", port), } return s } func main() { - var port = flag.Int("port", 8080, "Port for test HTTP server") + port := flag.String("port", "8080", "Port for test HTTP server") flag.Parse() // Create an instance of our handler which satisfies the generated interface petStore := api.NewPetStore() diff --git a/examples/petstore-expanded/gin/petstore_test.go b/examples/petstore-expanded/gin/petstore_test.go index 01fc944a12..d7a557d1cb 100644 --- a/examples/petstore-expanded/gin/petstore_test.go +++ b/examples/petstore-expanded/gin/petstore_test.go @@ -7,8 +7,8 @@ import ( "net/http/httptest" "testing" - "github.com/deepmap/oapi-codegen/examples/petstore-expanded/gin/api" - "github.com/deepmap/oapi-codegen/pkg/testutil" + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/gin/api" + "github.com/oapi-codegen/testutil" "github.com/stretchr/testify/assert" ) @@ -20,7 +20,7 @@ func doGet(t *testing.T, handler http.Handler, url string) *httptest.ResponseRec func TestPetStore(t *testing.T) { var err error store := api.NewPetStore() - ginPetServer := NewGinPetServer(store, 8080) + ginPetServer := NewGinPetServer(store, "8080") r := ginPetServer.Handler t.Run("Add pet", func(t *testing.T) { @@ -35,7 +35,7 @@ func TestPetStore(t *testing.T) { var resultPet api.Pet err = json.NewDecoder(rr.Body).Decode(&resultPet) - assert.NoError(t, err, "error unmarshalling response") + assert.NoError(t, err, "error unmarshaling response") assert.Equal(t, newPet.Name, resultPet.Name) assert.Equal(t, *newPet.Tag, *resultPet.Tag) }) @@ -119,7 +119,7 @@ func TestPetStore(t *testing.T) { var petError api.Error err = json.NewDecoder(rr.Body).Decode(&petError) - assert.NoError(t, err, "error unmarshalling PetError") + assert.NoError(t, err, "error unmarshaling PetError") assert.Equal(t, int32(http.StatusNotFound), petError.Code) // Now, delete both real pets diff --git a/examples/petstore-expanded/gorilla/api/cfg.yaml b/examples/petstore-expanded/gorilla/api/cfg.yaml index 8ead7a60de..0034869958 100644 --- a/examples/petstore-expanded/gorilla/api/cfg.yaml +++ b/examples/petstore-expanded/gorilla/api/cfg.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: api generate: gorilla-server: true diff --git a/examples/petstore-expanded/gorilla/api/petstore.gen.go b/examples/petstore-expanded/gorilla/api/petstore.gen.go index b55ecd0142..0ccc7da0b5 100644 --- a/examples/petstore-expanded/gorilla/api/petstore.gen.go +++ b/examples/petstore-expanded/gorilla/api/petstore.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api import ( @@ -13,9 +13,9 @@ import ( "path" "strings" - "github.com/deepmap/oapi-codegen/pkg/runtime" "github.com/getkin/kin-openapi/openapi3" "github.com/gorilla/mux" + "github.com/oapi-codegen/runtime" ) // Error defines model for Error. @@ -83,11 +83,10 @@ type ServerInterfaceWrapper struct { ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) } -type MiddlewareFunc func(http.HandlerFunc) http.HandlerFunc +type MiddlewareFunc func(http.Handler) http.Handler // FindPets operation middleware func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() var err error @@ -110,82 +109,79 @@ func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Reque return } - var handler = func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.FindPets(w, r, params) - } + })) for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- { handler = siw.HandlerMiddlewares[i](handler) } - handler(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // AddPet operation middleware func (siw *ServerInterfaceWrapper) AddPet(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var handler = func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.AddPet(w, r) - } + })) for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- { handler = siw.HandlerMiddlewares[i](handler) } - handler(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // DeletePet operation middleware func (siw *ServerInterfaceWrapper) DeletePet(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() var err error // ------------- Path parameter "id" ------------- var id int64 - err = runtime.BindStyledParameter("simple", false, "id", mux.Vars(r)["id"], &id) + err = runtime.BindStyledParameterWithOptions("simple", "id", mux.Vars(r)["id"], &id, runtime.BindStyledParameterOptions{Explode: false, Required: true}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) return } - var handler = func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.DeletePet(w, r, id) - } + })) for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- { handler = siw.HandlerMiddlewares[i](handler) } - handler(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // FindPetByID operation middleware func (siw *ServerInterfaceWrapper) FindPetByID(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() var err error // ------------- Path parameter "id" ------------- var id int64 - err = runtime.BindStyledParameter("simple", false, "id", mux.Vars(r)["id"], &id) + err = runtime.BindStyledParameterWithOptions("simple", "id", mux.Vars(r)["id"], &id, runtime.BindStyledParameterOptions{Explode: false, Required: true}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) return } - var handler = func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.FindPetByID(w, r, id) - } + })) for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- { handler = siw.HandlerMiddlewares[i](handler) } - handler(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } type UnescapedCookieParamError struct { @@ -201,16 +197,16 @@ func (e *UnescapedCookieParamError) Unwrap() error { return e.Err } -type UnmarshallingParamError struct { +type UnmarshalingParamError struct { ParamName string Err error } -func (e *UnmarshallingParamError) Error() string { - return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) } -func (e *UnmarshallingParamError) Unwrap() error { +func (e *UnmarshalingParamError) Unwrap() error { return e.Err } @@ -350,16 +346,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -377,7 +373,7 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } @@ -391,12 +387,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/examples/petstore-expanded/gorilla/api/petstore.go b/examples/petstore-expanded/gorilla/api/petstore.go index 838c961165..6ede559ec5 100644 --- a/examples/petstore-expanded/gorilla/api/petstore.go +++ b/examples/petstore-expanded/gorilla/api/petstore.go @@ -1,4 +1,4 @@ -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=cfg.yaml ../../petstore-expanded.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=cfg.yaml ../../petstore-expanded.yaml package api @@ -26,7 +26,7 @@ func NewPetStore() *PetStore { } } -// This function wraps sending of an error in the Error format, and +// sendPetStoreError wraps sending of an error in the Error format, and // handling the failure to marshal that. func sendPetStoreError(w http.ResponseWriter, code int, message string) { petErr := Error{ @@ -34,7 +34,7 @@ func sendPetStoreError(w http.ResponseWriter, code int, message string) { Message: message, } w.WriteHeader(code) - json.NewEncoder(w).Encode(petErr) + _ = json.NewEncoder(w).Encode(petErr) } // FindPets implements all the handlers in the ServerInterface @@ -67,7 +67,7 @@ func (p *PetStore) FindPets(w http.ResponseWriter, r *http.Request, params FindP } w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(result) + _ = json.NewEncoder(w).Encode(result) } func (p *PetStore) AddPet(w http.ResponseWriter, r *http.Request) { @@ -89,14 +89,14 @@ func (p *PetStore) AddPet(w http.ResponseWriter, r *http.Request) { pet.Name = newPet.Name pet.Tag = newPet.Tag pet.Id = p.NextId - p.NextId = p.NextId + 1 + p.NextId++ // Insert into map p.Pets[pet.Id] = pet // Now, we have to return the NewPet w.WriteHeader(http.StatusCreated) - json.NewEncoder(w).Encode(pet) + _ = json.NewEncoder(w).Encode(pet) } func (p *PetStore) FindPetByID(w http.ResponseWriter, r *http.Request, id int64) { @@ -110,7 +110,7 @@ func (p *PetStore) FindPetByID(w http.ResponseWriter, r *http.Request, id int64) } w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(pet) + _ = json.NewEncoder(w).Encode(pet) } func (p *PetStore) DeletePet(w http.ResponseWriter, r *http.Request, id int64) { diff --git a/examples/petstore-expanded/gorilla/petstore.go b/examples/petstore-expanded/gorilla/petstore.go index dfd01180a4..d1675f1ae3 100644 --- a/examples/petstore-expanded/gorilla/petstore.go +++ b/examples/petstore-expanded/gorilla/petstore.go @@ -8,16 +8,17 @@ import ( "flag" "fmt" "log" + "net" "net/http" "os" - "github.com/deepmap/oapi-codegen/examples/petstore-expanded/gorilla/api" - middleware "github.com/deepmap/oapi-codegen/pkg/chi-middleware" "github.com/gorilla/mux" + middleware "github.com/oapi-codegen/nethttp-middleware" + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/gorilla/api" ) func main() { - var port = flag.Int("port", 8080, "Port for test HTTP server") + port := flag.String("port", "8080", "Port for test HTTP server") flag.Parse() swagger, err := api.GetSwagger() @@ -45,7 +46,7 @@ func main() { s := &http.Server{ Handler: r, - Addr: fmt.Sprintf("0.0.0.0:%d", *port), + Addr: net.JoinHostPort("0.0.0.0", *port), } // And we serve HTTP until the world ends. diff --git a/examples/petstore-expanded/gorilla/petstore_test.go b/examples/petstore-expanded/gorilla/petstore_test.go index b0b52b3e93..a6cfedecd8 100644 --- a/examples/petstore-expanded/gorilla/petstore_test.go +++ b/examples/petstore-expanded/gorilla/petstore_test.go @@ -7,10 +7,10 @@ import ( "net/http/httptest" "testing" - "github.com/deepmap/oapi-codegen/examples/petstore-expanded/gorilla/api" - middleware "github.com/deepmap/oapi-codegen/pkg/chi-middleware" - "github.com/deepmap/oapi-codegen/pkg/testutil" "github.com/gorilla/mux" + middleware "github.com/oapi-codegen/nethttp-middleware" + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/gorilla/api" + "github.com/oapi-codegen/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -53,7 +53,7 @@ func TestPetStore(t *testing.T) { var resultPet api.Pet err = json.NewDecoder(rr.Body).Decode(&resultPet) - assert.NoError(t, err, "error unmarshalling response") + assert.NoError(t, err, "error unmarshaling response") assert.Equal(t, newPet.Name, resultPet.Name) assert.Equal(t, *newPet.Tag, *resultPet.Tag) }) @@ -146,7 +146,7 @@ func TestPetStore(t *testing.T) { var petError api.Error err = json.NewDecoder(rr.Body).Decode(&petError) - assert.NoError(t, err, "error unmarshalling PetError") + assert.NoError(t, err, "error unmarshaling PetError") assert.Equal(t, int32(http.StatusNotFound), petError.Code) // Now, delete both real pets diff --git a/examples/petstore-expanded/internal/config.yaml b/examples/petstore-expanded/internal/config.yaml index 0678280542..33b649fd81 100644 --- a/examples/petstore-expanded/internal/config.yaml +++ b/examples/petstore-expanded/internal/config.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../configuration-schema.json package: petstore generate: client: true diff --git a/examples/petstore-expanded/internal/doc.go b/examples/petstore-expanded/internal/doc.go index b255193076..b04cac3f8d 100644 --- a/examples/petstore-expanded/internal/doc.go +++ b/examples/petstore-expanded/internal/doc.go @@ -17,4 +17,4 @@ package internal // server. The file petstore.gen.go is automatically generated from the schema // Run oapi-codegen to regenerate the petstore boilerplate -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml ../petstore-expanded.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../petstore-expanded.yaml diff --git a/examples/petstore-expanded/iris/api/petstore-server.gen.go b/examples/petstore-expanded/iris/api/petstore-server.gen.go new file mode 100644 index 0000000000..a559c238d5 --- /dev/null +++ b/examples/petstore-expanded/iris/api/petstore-server.gen.go @@ -0,0 +1,247 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/kataras/iris/v12" + "github.com/oapi-codegen/runtime" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Returns all pets + // (GET /pets) + FindPets(ctx iris.Context, params FindPetsParams) + // Creates a new pet + // (POST /pets) + AddPet(ctx iris.Context) + // Deletes a pet by ID + // (DELETE /pets/{id}) + DeletePet(ctx iris.Context, id int64) + // Returns a pet by ID + // (GET /pets/{id}) + FindPetByID(ctx iris.Context, id int64) +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +type MiddlewareFunc iris.Handler + +// FindPets converts iris context to params. +func (w *ServerInterfaceWrapper) FindPets(ctx iris.Context) { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params FindPetsParams + // ------------- Optional query parameter "tags" ------------- + + err = runtime.BindQueryParameter("form", true, false, "tags", ctx.Request().URL.Query(), ¶ms.Tags) + if err != nil { + ctx.StatusCode(http.StatusBadRequest) + ctx.Writef("Invalid format for parameter tags: %s", err) + return + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.Request().URL.Query(), ¶ms.Limit) + if err != nil { + ctx.StatusCode(http.StatusBadRequest) + ctx.Writef("Invalid format for parameter limit: %s", err) + return + } + + // Invoke the callback with all the unmarshaled arguments + w.Handler.FindPets(ctx, params) +} + +// AddPet converts iris context to params. +func (w *ServerInterfaceWrapper) AddPet(ctx iris.Context) { + + // Invoke the callback with all the unmarshaled arguments + w.Handler.AddPet(ctx) +} + +// DeletePet converts iris context to params. +func (w *ServerInterfaceWrapper) DeletePet(ctx iris.Context) { + + var err error + + // ------------- Path parameter "id" ------------- + var id int64 + + err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Params().Get("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + ctx.StatusCode(http.StatusBadRequest) + ctx.Writef("Invalid format for parameter id: %s", err) + return + } + + // Invoke the callback with all the unmarshaled arguments + w.Handler.DeletePet(ctx, id) +} + +// FindPetByID converts iris context to params. +func (w *ServerInterfaceWrapper) FindPetByID(ctx iris.Context) { + + var err error + + // ------------- Path parameter "id" ------------- + var id int64 + + err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Params().Get("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + ctx.StatusCode(http.StatusBadRequest) + ctx.Writef("Invalid format for parameter id: %s", err) + return + } + + // Invoke the callback with all the unmarshaled arguments + w.Handler.FindPetByID(ctx, id) +} + +// IrisServerOption is the option for iris server +type IrisServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router *iris.Application, si ServerInterface) { + RegisterHandlersWithOptions(router, si, IrisServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router *iris.Application, si ServerInterface, options IrisServerOptions) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.Get(options.BaseURL+"/pets", wrapper.FindPets) + router.Post(options.BaseURL+"/pets", wrapper.AddPet) + router.Delete(options.BaseURL+"/pets/:id", wrapper.DeletePet) + router.Get(options.BaseURL+"/pets/:id", wrapper.FindPetByID) + + router.Build() +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/+RXW48budH9KwV+32OnNbEXedBTvB4vICBrT+LdvKznoYZdkmrBSw9Z1FgY6L8HRbZu", + "I3k2QYIgQV506WY1T51zqlj9bGz0YwwUJJv5s8l2TR7rzw8pxaQ/xhRHSsJUL9s4kH4PlG3iUTgGM2+L", + "od7rzDImj2LmhoO8fWM6I9uR2l9aUTK7znjKGVfffND+9iE0S+KwMrtdZxI9Fk40mPkvZtpwv/x+15mP", + "9HRHcok7oL+y3Uf0BHEJsiYYSS437Izg6jLup+34etwLoHV3hTdhQ+c+Lc38l2fz/4mWZm7+b3YUYjap", + "MJty2XUvk+HhEtLPgR8LAQ/nuE7F+MN3V8R4gZQHc7+73+llDsvYJA+CtuImj+zM3ODIQuj/mJ9wtaLU", + "czTdRLH53K7Bu7sF/EToTWdK0qC1yJjns9lJ0K57kcU7yOhHRzVa1ihQMmVAzSZLTASYAQPQ17ZMIgzk", + "Y8iSUAiWhFISZeBQOfg0UtAnve1vII9keckW61adcWwpZDqaw7wb0a4J3vQ3F5ifnp56rLf7mFazKTbP", + "/rR4/+Hj5w+/e9Pf9GvxrjqGks+flp8pbdjS1cRndc1M5WBxp6zdTXmazmwo5cbK7/ub/kYfHUcKOLKZ", + "m7f1UmdGlHX1xEwZ0h+rZrFzXv9CUlLIgM5VKmGZoq8U5W0W8o1r/V8yJVgry9ZSziDxS/iIHjINYGMY", + "2FOQ4oGy9PAjkqWAGYT8GBNkXLEIZ8g4MoUOAllI6xhsyZDJnyxgAfQkPbyjQBgABVYJNzwgYFkV6gAt", + "MNriuIb28L4kfGApCeLAEVxM5DuIKWAioBUJkKMJXSDbgS0pl6wl4chKyT3cFs7gGaSkkXMHY3EbDph0", + "L0pRk+5AOFgeShDYYOKS4deSJfawCLBGC2sFgTkTjA6FEAa2UrzSsWhFpbngwCNny2EFGESzOebueFUc", + "HjIf15hIEu5J1PXgo6MsTMB+pDSwMvVX3qBvCaHjx4IeBkZlJmGGR81tQ44FQgwgMUlMSgkvKQyH3Xu4", + "S0iZgihMCuyPAEoKCJvoiowosKFAARVwI1c/PJakz1iE45OXlCbWl2jZcT7bpO6gH91RXws5DuhIhR06", + "5dFSQtHE9LuHzyWPFAZWlh2qeYboYurUgZmsqJtrltUqmnUHG1qzLQ5BW1saigfHD5RiDz/G9MBAhbOP", + "w6kMersa26HlwNh/CV/CZxqqEiXDktR8Lj7EVAMoHh2TiqTie9Da8FgfOJHP2XVA5axamuTgivpQ3dnD", + "3RozOdcKY6Q0hVeaq7wksMRi+aE0wnG/j647jd+Qm6TjDaWE3fnWWifAQ3coxMAP6x5+FhjJOQpCWU+O", + "MeZCWkn7IupBqcB9FWjR7bncP2mfVmWyq0AOtgglWJDEWerBtGFB6uGHki0BSe0GQ+FDFWinyJYcJa5w", + "mn/3AV7dUrCaxxafMYDHlaZMblKrhz+XFuqjU92aelSad45QukPzASxWi6StnOzZ0p7MMTWZQzWqWVRg", + "4NAdoUyFGzjzHnBWDJalDKxQc0YosvfZJGTb6Yy0ul8Pd6fCVOYmjGMi4eJPOlczTelO/K2tt/+iZ5wO", + "DfW8Wwxmbn7gMOj5Uo+NpARQynUKOT8sBFfa92HJTijBw9boMGDm5rFQ2h5Pel1numlorHOJkK9n0OUU", + "1S5gSrjV/1m29djT8aQOOOcIPH5lr228+AdKOtEkysVJhZXqWfYNTI49yxmo3xxHd/c6AuVRW0tF/+bm", + "Zj/3UGjz2ji6aXKY/ZoV4vO1tF8b5tok94KI3cUANJLAHkwbj5ZYnPxDeF6D0cb6KxuXQF9Hba3ag9ua", + "zuTiPabtlQFCsY0xXxk13idCqTNboCddux/G6lyjZ3DDrkt0nnMuPtFwYdZ3g3rVtOmUsnwfh+2/jIX9", + "ZH1Jwx2JegyHQb8OsM3plCyp0O6f9MxvWuW/xxoXgtf7dR6dPfOwaxZxJFdewNp1jc0cVq6+tcADapuN", + "zTWLW8hFc7rikdsa3Wzyakdb3GoPGZu2E5apf+gAfWwfPFwo/a1ecv1t6rKXfHeZtQJpKIb/JCFvD2JU", + "FbawuFV4r79QnCt20HFx+63j5/ttvff367Ukset/m1z/s2X8QtGmfl1CabOX6fyteP9S3p+82err6e5+", + "97cAAAD//ykDnxlaEgAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/examples/petstore-expanded/iris/api/petstore-types.gen.go b/examples/petstore-expanded/iris/api/petstore-types.gen.go new file mode 100644 index 0000000000..0e4954f349 --- /dev/null +++ b/examples/petstore-expanded/iris/api/petstore-types.gen.go @@ -0,0 +1,46 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +// Error defines model for Error. +type Error struct { + // Code Error code + Code int32 `json:"code"` + + // Message Error message + Message string `json:"message"` +} + +// NewPet defines model for NewPet. +type NewPet struct { + // Name Name of the pet + Name string `json:"name"` + + // Tag Type of the pet + Tag *string `json:"tag,omitempty"` +} + +// Pet defines model for Pet. +type Pet struct { + // Id Unique id of the pet + Id int64 `json:"id"` + + // Name Name of the pet + Name string `json:"name"` + + // Tag Type of the pet + Tag *string `json:"tag,omitempty"` +} + +// FindPetsParams defines parameters for FindPets. +type FindPetsParams struct { + // Tags tags to filter by + Tags *[]string `form:"tags,omitempty" json:"tags,omitempty"` + + // Limit maximum number of results to return + Limit *int32 `form:"limit,omitempty" json:"limit,omitempty"` +} + +// AddPetJSONRequestBody defines body for AddPet for application/json ContentType. +type AddPetJSONRequestBody = NewPet diff --git a/examples/petstore-expanded/iris/api/petstore.go b/examples/petstore-expanded/iris/api/petstore.go new file mode 100644 index 0000000000..d1e8018012 --- /dev/null +++ b/examples/petstore-expanded/iris/api/petstore.go @@ -0,0 +1,132 @@ +// Copyright 2019 DeepMap, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=types.cfg.yaml ../../petstore-expanded.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=server.cfg.yaml ../../petstore-expanded.yaml + +package api + +import ( + "fmt" + "net/http" + "sync" + + "github.com/kataras/iris/v12" +) + +type PetStore struct { + Pets map[int64]Pet + NextId int64 + Lock sync.Mutex +} + +func NewPetStore() *PetStore { + return &PetStore{ + Pets: make(map[int64]Pet), + NextId: 1000, + } +} + +// This function wraps sending of an error in the Error format, and +// handling the failure to marshal that. +func sendPetStoreError(c iris.Context, code int, message string) { + petErr := Error{ + Code: int32(code), + Message: message, + } + _ = c.StopWithJSON(code, petErr) +} + +// FindPets implements all the handlers in the ServerInterface +func (p *PetStore) FindPets(c iris.Context, params FindPetsParams) { + p.Lock.Lock() + defer p.Lock.Unlock() + + var result []Pet + + for _, pet := range p.Pets { + if params.Tags != nil { + // If we have tags, filter pets by tag + for _, t := range *params.Tags { + if pet.Tag != nil && (*pet.Tag == t) { + result = append(result, pet) + } + } + } else { + // Add all pets if we're not filtering + result = append(result, pet) + } + + if params.Limit != nil { + l := int(*params.Limit) + if len(result) >= l { + // We're at the limit + break + } + } + } + _ = c.StopWithJSON(http.StatusOK, result) +} + +func (p *PetStore) AddPet(c iris.Context) { + // We expect a NewPet object in the request body. + var newPet NewPet + err := c.ReadJSON(&newPet) + if err != nil { + sendPetStoreError(c, http.StatusBadRequest, "Invalid format for NewPet") + return + } + // We now have a pet, let's add it to our "database". + + // We're always asynchronous, so lock unsafe operations below + p.Lock.Lock() + defer p.Lock.Unlock() + + // We handle pets, not NewPets, which have an additional ID field + var pet Pet + pet.Name = newPet.Name + pet.Tag = newPet.Tag + pet.Id = p.NextId + p.NextId = p.NextId + 1 + + // Insert into map + p.Pets[pet.Id] = pet + + // Now, we have to return the NewPet + _ = c.StopWithJSON(http.StatusCreated, pet) +} + +func (p *PetStore) FindPetByID(c iris.Context, petId int64) { + p.Lock.Lock() + defer p.Lock.Unlock() + + pet, found := p.Pets[petId] + if !found { + sendPetStoreError(c, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", petId)) + return + } + _ = c.StopWithJSON(http.StatusOK, pet) +} + +func (p *PetStore) DeletePet(c iris.Context, id int64) { + p.Lock.Lock() + defer p.Lock.Unlock() + + _, found := p.Pets[id] + if !found { + sendPetStoreError(c, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", id)) + } + delete(p.Pets, id) + c.StatusCode(http.StatusNoContent) +} diff --git a/examples/petstore-expanded/iris/api/server.cfg.yaml b/examples/petstore-expanded/iris/api/server.cfg.yaml new file mode 100644 index 0000000000..f8bb40c5bd --- /dev/null +++ b/examples/petstore-expanded/iris/api/server.cfg.yaml @@ -0,0 +1,6 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +generate: + iris-server: true + embedded-spec: true +output: petstore-server.gen.go diff --git a/examples/petstore-expanded/iris/api/types.cfg.yaml b/examples/petstore-expanded/iris/api/types.cfg.yaml new file mode 100644 index 0000000000..cf5128b726 --- /dev/null +++ b/examples/petstore-expanded/iris/api/types.cfg.yaml @@ -0,0 +1,5 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +generate: + models: true +output: petstore-types.gen.go diff --git a/examples/petstore-expanded/iris/petstore.go b/examples/petstore-expanded/iris/petstore.go new file mode 100644 index 0000000000..2b2dea37f7 --- /dev/null +++ b/examples/petstore-expanded/iris/petstore.go @@ -0,0 +1,50 @@ +// This is an example of implementing the Pet Store from the OpenAPI documentation +// found at: +// https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/petstore.yaml +// +// The code under api/petstore/ has been generated from that specification. +package main + +import ( + "flag" + "fmt" + "log" + "os" + + "github.com/kataras/iris/v12" + middleware "github.com/oapi-codegen/iris-middleware" + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/iris/api" +) + +func NewIrisPetServer(petStore *api.PetStore, port int) *iris.Application { + swagger, err := api.GetSwagger() + if err != nil { + fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err) + os.Exit(1) + } + + // Clear out the servers array in the swagger spec, that skips validating + // that server names match. We don't know how this thing will be run. + swagger.Servers = nil + + i := iris.Default() + + // Use our validation middleware to check all requests against the + // OpenAPI schema. + i.Use(middleware.OapiRequestValidator(swagger)) + + api.RegisterHandlers(i, petStore) + + return i +} + +func main() { + port := flag.Int("port", 8080, "Port for test HTTP server") + flag.Parse() + // Create an instance of our handler which satisfies the generated interface + petStore := api.NewPetStore() + s := NewIrisPetServer(petStore, *port) + + // And we serve HTTP until the world ends. + log.Fatal(s.Listen(fmt.Sprintf("localhost:%d", *port))) +} diff --git a/examples/petstore-expanded/iris/petstore_test.go b/examples/petstore-expanded/iris/petstore_test.go new file mode 100644 index 0000000000..837a53369c --- /dev/null +++ b/examples/petstore-expanded/iris/petstore_test.go @@ -0,0 +1,151 @@ +// Copyright 2019 DeepMap, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/iris/api" + "github.com/oapi-codegen/testutil" + "github.com/stretchr/testify/assert" +) + +func doGet(t *testing.T, handler http.Handler, url string) *httptest.ResponseRecorder { + response := testutil.NewRequest().Get(url).WithAcceptJson().GoWithHTTPHandler(t, handler) + return response.Recorder +} + +func TestPetStore(t *testing.T) { + store := api.NewPetStore() + irisPetServer := NewIrisPetServer(store, 8080) + + t.Run("Add pet", func(t *testing.T) { + tag := "TagOfSpot" + newPet := api.NewPet{ + Name: "Spot", + Tag: &tag, + } + + rr := testutil.NewRequest().Post("/pets").WithJsonBody(newPet).GoWithHTTPHandler(t, irisPetServer).Recorder + assert.Equal(t, http.StatusCreated, rr.Code) + + var resultPet api.Pet + err := json.NewDecoder(rr.Body).Decode(&resultPet) + assert.NoError(t, err, "error unmarshaling response") + assert.Equal(t, newPet.Name, resultPet.Name) + assert.Equal(t, *newPet.Tag, *resultPet.Tag) + }) + + t.Run("Find pet by ID", func(t *testing.T) { + pet := api.Pet{ + Id: 100, + } + store.Pets[pet.Id] = pet + rr := doGet(t, irisPetServer, fmt.Sprintf("/pets/%d", pet.Id)) + + var resultPet api.Pet + err := json.NewDecoder(rr.Body).Decode(&resultPet) + assert.NoError(t, err, "error getting pet") + assert.Equal(t, pet, resultPet) + }) + + t.Run("Pet not found", func(t *testing.T) { + rr := doGet(t, irisPetServer, "/pets/27179095781") + assert.Equal(t, http.StatusNotFound, rr.Code) + + var petError api.Error + err := json.NewDecoder(rr.Body).Decode(&petError) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, int32(http.StatusNotFound), petError.Code) + }) + + t.Run("List all pets", func(t *testing.T) { + store.Pets = map[int64]api.Pet{1: {}, 2: {}} + + // Now, list all pets, we should have two + rr := doGet(t, irisPetServer, "/pets") + assert.Equal(t, http.StatusOK, rr.Code) + + var petList []api.Pet + err := json.NewDecoder(rr.Body).Decode(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 2, len(petList)) + }) + + t.Run("Filter pets by tag", func(t *testing.T) { + tag := "TagOfFido" + + store.Pets = map[int64]api.Pet{ + 1: { + Tag: &tag, + }, + 2: {}, + } + + // Filter pets by tag, we should have 1 + rr := doGet(t, irisPetServer, "/pets?tags=TagOfFido") + assert.Equal(t, http.StatusOK, rr.Code) + + var petList []api.Pet + err := json.NewDecoder(rr.Body).Decode(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 1, len(petList)) + }) + + t.Run("Filter pets by tag", func(t *testing.T) { + store.Pets = map[int64]api.Pet{1: {}, 2: {}} + + // Filter pets by non existent tag, we should have 0 + rr := doGet(t, irisPetServer, "/pets?tags=NotExists") + assert.Equal(t, http.StatusOK, rr.Code) + + var petList []api.Pet + err := json.NewDecoder(rr.Body).Decode(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 0, len(petList)) + }) + + t.Run("Delete pets", func(t *testing.T) { + store.Pets = map[int64]api.Pet{1: {}, 2: {}} + + // Let's delete non-existent pet + rr := testutil.NewRequest().Delete("/pets/7").GoWithHTTPHandler(t, irisPetServer).Recorder + assert.Equal(t, http.StatusNotFound, rr.Code) + + var petError api.Error + err := json.NewDecoder(rr.Body).Decode(&petError) + assert.NoError(t, err, "error unmarshaling PetError") + assert.Equal(t, int32(http.StatusNotFound), petError.Code) + + // Now, delete both real pets + rr = testutil.NewRequest().Delete("/pets/1").GoWithHTTPHandler(t, irisPetServer).Recorder + assert.Equal(t, http.StatusNoContent, rr.Code) + + rr = testutil.NewRequest().Delete("/pets/2").GoWithHTTPHandler(t, irisPetServer).Recorder + assert.Equal(t, http.StatusNoContent, rr.Code) + + // Should have no pets left. + var petList []api.Pet + rr = doGet(t, irisPetServer, "/pets") + assert.Equal(t, http.StatusOK, rr.Code) + err = json.NewDecoder(rr.Body).Decode(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 0, len(petList)) + }) +} diff --git a/examples/petstore-expanded/petstore-client.gen.go b/examples/petstore-expanded/petstore-client.gen.go index 0929a64096..9bd928f497 100644 --- a/examples/petstore-expanded/petstore-client.gen.go +++ b/examples/petstore-expanded/petstore-client.gen.go @@ -1,6 +1,6 @@ // Package petstore provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package petstore import ( @@ -13,7 +13,7 @@ import ( "net/url" "strings" - "github.com/deepmap/oapi-codegen/pkg/runtime" + "github.com/oapi-codegen/runtime" ) // Error defines model for Error. @@ -134,7 +134,7 @@ type ClientInterface interface { // FindPets request FindPets(ctx context.Context, params *FindPetsParams, reqEditors ...RequestEditorFn) (*http.Response, error) - // AddPet request with any body + // AddPetWithBody request with any body AddPetWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) AddPet(ctx context.Context, body AddPetJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -225,42 +225,44 @@ func NewFindPetsRequest(server string, params *FindPetsParams) (*http.Request, e return nil, err } - queryValues := queryURL.Query() + if params != nil { + queryValues := queryURL.Query() - if params.Tags != nil { + if params.Tags != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "tags", runtime.ParamLocationQuery, *params.Tags); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "tags", runtime.ParamLocationQuery, *params.Tags); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } - } - } - - if params.Limit != nil { + } - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "limit", runtime.ParamLocationQuery, *params.Limit); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if params.Limit != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "limit", runtime.ParamLocationQuery, *params.Limit); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } + } + queryURL.RawQuery = queryValues.Encode() } - queryURL.RawQuery = queryValues.Encode() - req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -420,18 +422,18 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { - // FindPets request + // FindPetsWithResponse request FindPetsWithResponse(ctx context.Context, params *FindPetsParams, reqEditors ...RequestEditorFn) (*FindPetsResponse, error) - // AddPet request with any body + // AddPetWithBodyWithResponse request with any body AddPetWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AddPetResponse, error) AddPetWithResponse(ctx context.Context, body AddPetJSONRequestBody, reqEditors ...RequestEditorFn) (*AddPetResponse, error) - // DeletePet request + // DeletePetWithResponse request DeletePetWithResponse(ctx context.Context, id int64, reqEditors ...RequestEditorFn) (*DeletePetResponse, error) - // FindPetByID request + // FindPetByIDWithResponse request FindPetByIDWithResponse(ctx context.Context, id int64, reqEditors ...RequestEditorFn) (*FindPetByIDResponse, error) } diff --git a/examples/petstore-expanded/stdhttp/Makefile b/examples/petstore-expanded/stdhttp/Makefile new file mode 100644 index 0000000000..5ec0edd058 --- /dev/null +++ b/examples/petstore-expanded/stdhttp/Makefile @@ -0,0 +1,17 @@ +lint: + $(GOBIN)/golangci-lint run ./... + +lint-ci: + $(GOBIN)/golangci-lint run ./... --output.text.path=stdout --timeout=5m + +generate: + go generate ./... + +test: + go test -cover ./... + +tidy: + go mod tidy + +tidy-ci: + tidied -verbose diff --git a/examples/petstore-expanded/stdhttp/api/cfg.yaml b/examples/petstore-expanded/stdhttp/api/cfg.yaml new file mode 100644 index 0000000000..12339c51fa --- /dev/null +++ b/examples/petstore-expanded/stdhttp/api/cfg.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +generate: + std-http-server: true + embedded-spec: true + models: true +output: petstore.gen.go diff --git a/examples/petstore-expanded/stdhttp/api/petstore.gen.go b/examples/petstore-expanded/stdhttp/api/petstore.gen.go new file mode 100644 index 0000000000..4f0c52be9d --- /dev/null +++ b/examples/petstore-expanded/stdhttp/api/petstore.gen.go @@ -0,0 +1,419 @@ +//go:build go1.22 + +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/oapi-codegen/runtime" +) + +// Error defines model for Error. +type Error struct { + // Code Error code + Code int32 `json:"code"` + + // Message Error message + Message string `json:"message"` +} + +// NewPet defines model for NewPet. +type NewPet struct { + // Name Name of the pet + Name string `json:"name"` + + // Tag Type of the pet + Tag *string `json:"tag,omitempty"` +} + +// Pet defines model for Pet. +type Pet struct { + // Id Unique id of the pet + Id int64 `json:"id"` + + // Name Name of the pet + Name string `json:"name"` + + // Tag Type of the pet + Tag *string `json:"tag,omitempty"` +} + +// FindPetsParams defines parameters for FindPets. +type FindPetsParams struct { + // Tags tags to filter by + Tags *[]string `form:"tags,omitempty" json:"tags,omitempty"` + + // Limit maximum number of results to return + Limit *int32 `form:"limit,omitempty" json:"limit,omitempty"` +} + +// AddPetJSONRequestBody defines body for AddPet for application/json ContentType. +type AddPetJSONRequestBody = NewPet + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Returns all pets + // (GET /pets) + FindPets(w http.ResponseWriter, r *http.Request, params FindPetsParams) + // Creates a new pet + // (POST /pets) + AddPet(w http.ResponseWriter, r *http.Request) + // Deletes a pet by ID + // (DELETE /pets/{id}) + DeletePet(w http.ResponseWriter, r *http.Request, id int64) + // Returns a pet by ID + // (GET /pets/{id}) + FindPetByID(w http.ResponseWriter, r *http.Request, id int64) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// FindPets operation middleware +func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Request) { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params FindPetsParams + + // ------------- Optional query parameter "tags" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "tags", r.URL.Query(), ¶ms.Tags, runtime.BindQueryParameterOptions{Type: "array", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "tags", Err: err}) + return + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "limit", r.URL.Query(), ¶ms.Limit, runtime.BindQueryParameterOptions{Type: "integer", Format: "int32"}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "limit", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.FindPets(w, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// AddPet operation middleware +func (siw *ServerInterfaceWrapper) AddPet(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.AddPet(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// DeletePet operation middleware +func (siw *ServerInterfaceWrapper) DeletePet(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id int64 + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "integer", Format: "int64"}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeletePet(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// FindPetByID operation middleware +func (siw *ServerInterfaceWrapper) FindPetByID(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id int64 + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "integer", Format: "int64"}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.FindPetByID(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{}) +} + +// ServeMux is an abstraction of http.ServeMux. +type ServeMux interface { + HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) + ServeHTTP(w http.ResponseWriter, r *http.Request) +} + +type StdHTTPServerOptions struct { + BaseURL string + BaseRouter ServeMux + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, m ServeMux) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{ + BaseRouter: m, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, m ServeMux, baseURL string) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{ + BaseURL: baseURL, + BaseRouter: m, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.Handler { + m := options.BaseRouter + + if m == nil { + m = http.NewServeMux() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + m.HandleFunc("GET "+options.BaseURL+"/pets", wrapper.FindPets) + m.HandleFunc("POST "+options.BaseURL+"/pets", wrapper.AddPet) + m.HandleFunc("DELETE "+options.BaseURL+"/pets/{id}", wrapper.DeletePet) + m.HandleFunc("GET "+options.BaseURL+"/pets/{id}", wrapper.FindPetByID) + + return m +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/+RXW48budH9KwV+32OnNbEXedBTvB4vICBrT+LdvKznoYZdkmrBSw9Z1FgY6L8HRbZu", + "I3k2QYIgQV506WY1T51zqlj9bGz0YwwUJJv5s8l2TR7rzw8pxaQ/xhRHSsJUL9s4kH4PlG3iUTgGM2+L", + "od7rzDImj2LmhoO8fWM6I9uR2l9aUTK7znjKGVfffND+9iE0S+KwMrtdZxI9Fk40mPkvZtpwv/x+15mP", + "9HRHcok7oL+y3Uf0BHEJsiYYSS437Izg6jLup+34etwLoHV3hTdhQ+c+Lc38l2fz/4mWZm7+b3YUYjap", + "MJty2XUvk+HhEtLPgR8LAQ/nuE7F+MN3V8R4gZQHc7+73+llDsvYJA+CtuImj+zM3ODIQuj/mJ9wtaLU", + "czTdRLH53K7Bu7sF/EToTWdK0qC1yJjns9lJ0K57kcU7yOhHRzVa1ihQMmVAzSZLTASYAQPQ17ZMIgzk", + "Y8iSUAiWhFISZeBQOfg0UtAnve1vII9keckW61adcWwpZDqaw7wb0a4J3vQ3F5ifnp56rLf7mFazKTbP", + "/rR4/+Hj5w+/e9Pf9GvxrjqGks+flp8pbdjS1cRndc1M5WBxp6zdTXmazmwo5cbK7/ub/kYfHUcKOLKZ", + "m7f1UmdGlHX1xEwZ0h+rZrFzXv9CUlLIgM5VKmGZoq8U5W0W8o1r/V8yJVgry9ZSziDxS/iIHjINYGMY", + "2FOQ4oGy9PAjkqWAGYT8GBNkXLEIZ8g4MoUOAllI6xhsyZDJnyxgAfQkPbyjQBgABVYJNzwgYFkV6gAt", + "MNriuIb28L4kfGApCeLAEVxM5DuIKWAioBUJkKMJXSDbgS0pl6wl4chKyT3cFs7gGaSkkXMHY3EbDph0", + "L0pRk+5AOFgeShDYYOKS4deSJfawCLBGC2sFgTkTjA6FEAa2UrzSsWhFpbngwCNny2EFGESzOebueFUc", + "HjIf15hIEu5J1PXgo6MsTMB+pDSwMvVX3qBvCaHjx4IeBkZlJmGGR81tQ44FQgwgMUlMSgkvKQyH3Xu4", + "S0iZgihMCuyPAEoKCJvoiowosKFAARVwI1c/PJakz1iE45OXlCbWl2jZcT7bpO6gH91RXws5DuhIhR06", + "5dFSQtHE9LuHzyWPFAZWlh2qeYboYurUgZmsqJtrltUqmnUHG1qzLQ5BW1saigfHD5RiDz/G9MBAhbOP", + "w6kMersa26HlwNh/CV/CZxqqEiXDktR8Lj7EVAMoHh2TiqTie9Da8FgfOJHP2XVA5axamuTgivpQ3dnD", + "3RozOdcKY6Q0hVeaq7wksMRi+aE0wnG/j647jd+Qm6TjDaWE3fnWWifAQ3coxMAP6x5+FhjJOQpCWU+O", + "MeZCWkn7IupBqcB9FWjR7bncP2mfVmWyq0AOtgglWJDEWerBtGFB6uGHki0BSe0GQ+FDFWinyJYcJa5w", + "mn/3AV7dUrCaxxafMYDHlaZMblKrhz+XFuqjU92aelSad45QukPzASxWi6StnOzZ0p7MMTWZQzWqWVRg", + "4NAdoUyFGzjzHnBWDJalDKxQc0YosvfZJGTb6Yy0ul8Pd6fCVOYmjGMi4eJPOlczTelO/K2tt/+iZ5wO", + "DfW8Wwxmbn7gMOj5Uo+NpARQynUKOT8sBFfa92HJTijBw9boMGDm5rFQ2h5Pel1numlorHOJkK9n0OUU", + "1S5gSrjV/1m29djT8aQOOOcIPH5lr228+AdKOtEkysVJhZXqWfYNTI49yxmo3xxHd/c6AuVRW0tF/+bm", + "Zj/3UGjz2ji6aXKY/ZoV4vO1tF8b5tok94KI3cUANJLAHkwbj5ZYnPxDeF6D0cb6KxuXQF9Hba3ag9ua", + "zuTiPabtlQFCsY0xXxk13idCqTNboCddux/G6lyjZ3DDrkt0nnMuPtFwYdZ3g3rVtOmUsnwfh+2/jIX9", + "ZH1Jwx2JegyHQb8OsM3plCyp0O6f9MxvWuW/xxoXgtf7dR6dPfOwaxZxJFdewNp1jc0cVq6+tcADapuN", + "zTWLW8hFc7rikdsa3Wzyakdb3GoPGZu2E5apf+gAfWwfPFwo/a1ecv1t6rKXfHeZtQJpKIb/JCFvD2JU", + "FbawuFV4r79QnCt20HFx+63j5/ttvff367Ukset/m1z/s2X8QtGmfl1CabOX6fyteP9S3p+82err6e5+", + "97cAAAD//ykDnxlaEgAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/examples/petstore-expanded/stdhttp/api/petstore.go b/examples/petstore-expanded/stdhttp/api/petstore.go new file mode 100644 index 0000000000..5b9d8606f1 --- /dev/null +++ b/examples/petstore-expanded/stdhttp/api/petstore.go @@ -0,0 +1,130 @@ +//go:build go1.22 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=cfg.yaml ../../petstore-expanded.yaml + +package api + +import ( + "encoding/json" + "fmt" + "net/http" + "sync" +) + +type PetStore struct { + Pets map[int64]Pet + NextId int64 + Lock sync.Mutex +} + +// Make sure we conform to ServerInterface + +var _ ServerInterface = (*PetStore)(nil) + +func NewPetStore() *PetStore { + return &PetStore{ + Pets: make(map[int64]Pet), + NextId: 1000, + } +} + +// sendPetStoreError wraps sending of an error in the Error format, and +// handling the failure to marshal that. +func sendPetStoreError(w http.ResponseWriter, code int, message string) { + petErr := Error{ + Code: int32(code), + Message: message, + } + w.WriteHeader(code) + _ = json.NewEncoder(w).Encode(petErr) +} + +// FindPets implements all the handlers in the ServerInterface +func (p *PetStore) FindPets(w http.ResponseWriter, r *http.Request, params FindPetsParams) { + p.Lock.Lock() + defer p.Lock.Unlock() + + var result []Pet + + for _, pet := range p.Pets { + if params.Tags != nil { + // If we have tags, filter pets by tag + for _, t := range *params.Tags { + if pet.Tag != nil && (*pet.Tag == t) { + result = append(result, pet) + } + } + } else { + // Add all pets if we're not filtering + result = append(result, pet) + } + + if params.Limit != nil { + l := int(*params.Limit) + if len(result) >= l { + // We're at the limit + break + } + } + } + + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(result) +} + +func (p *PetStore) AddPet(w http.ResponseWriter, r *http.Request) { + // We expect a NewPet object in the request body. + var newPet NewPet + if err := json.NewDecoder(r.Body).Decode(&newPet); err != nil { + sendPetStoreError(w, http.StatusBadRequest, "Invalid format for NewPet") + return + } + + // We now have a pet, let's add it to our "database". + + // We're always asynchronous, so lock unsafe operations below + p.Lock.Lock() + defer p.Lock.Unlock() + + // We handle pets, not NewPets, which have an additional ID field + var pet Pet + pet.Name = newPet.Name + pet.Tag = newPet.Tag + pet.Id = p.NextId + p.NextId++ + + // Insert into map + p.Pets[pet.Id] = pet + + // Now, we have to return the NewPet + w.WriteHeader(http.StatusCreated) + _ = json.NewEncoder(w).Encode(pet) +} + +func (p *PetStore) FindPetByID(w http.ResponseWriter, r *http.Request, id int64) { + p.Lock.Lock() + defer p.Lock.Unlock() + + pet, found := p.Pets[id] + if !found { + sendPetStoreError(w, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", id)) + return + } + + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(pet) +} + +func (p *PetStore) DeletePet(w http.ResponseWriter, r *http.Request, id int64) { + p.Lock.Lock() + defer p.Lock.Unlock() + + _, found := p.Pets[id] + if !found { + sendPetStoreError(w, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", id)) + return + } + delete(p.Pets, id) + + w.WriteHeader(http.StatusNoContent) +} diff --git a/examples/petstore-expanded/stdhttp/go.mod b/examples/petstore-expanded/stdhttp/go.mod new file mode 100644 index 0000000000..3922deb19e --- /dev/null +++ b/examples/petstore-expanded/stdhttp/go.mod @@ -0,0 +1,41 @@ +module github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/stdhttp + +go 1.24.3 + +replace github.com/oapi-codegen/oapi-codegen/v2 => ../../../ + +require ( + github.com/getkin/kin-openapi v0.133.0 + github.com/oapi-codegen/nethttp-middleware v1.1.2 + github.com/oapi-codegen/oapi-codegen/v2 v2.0.0-00010101000000-000000000000 + github.com/oapi-codegen/runtime v1.2.0 + github.com/oapi-codegen/testutil v1.1.0 + github.com/stretchr/testify v1.11.1 +) + +require ( + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/google/uuid v1.5.0 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/speakeasy-api/jsonpath v0.6.0 // indirect + github.com/speakeasy-api/openapi-overlay v0.10.3 // indirect + github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/tools v0.42.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/examples/petstore-expanded/stdhttp/go.sum b/examples/petstore-expanded/stdhttp/go.sum new file mode 100644 index 0000000000..677ca72245 --- /dev/null +++ b/examples/petstore-expanded/stdhttp/go.sum @@ -0,0 +1,189 @@ +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oapi-codegen/nethttp-middleware v1.1.2 h1:TQwEU3WM6ifc7ObBEtiJgbRPaCe513tvJpiMJjypVPA= +github.com/oapi-codegen/nethttp-middleware v1.1.2/go.mod h1:5qzjxMSiI8HjLljiOEjvs4RdrWyMPKnExeFS2kr8om4= +github.com/oapi-codegen/runtime v1.2.0 h1:RvKc1CVS1QeKSNzO97FBQbSMZyQ8s6rZd+LpmzwHMP4= +github.com/oapi-codegen/runtime v1.2.0/go.mod h1:Y7ZhmmlE8ikZOmuHRRndiIm7nf3xcVv+YMweKgG1DT0= +github.com/oapi-codegen/testutil v1.1.0 h1:EufqpNg43acR3qzr3ObhXmWg3Sl2kwtRnUN5GYY4d5g= +github.com/oapi-codegen/testutil v1.1.0/go.mod h1:ttCaYbHvJtHuiyeBF0tPIX+4uhEPTeizXKx28okijLw= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8= +github.com/speakeasy-api/jsonpath v0.6.0/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw= +github.com/speakeasy-api/openapi-overlay v0.10.3 h1:70een4vwHyslIp796vM+ox6VISClhtXsCjrQNhxwvWs= +github.com/speakeasy-api/openapi-overlay v0.10.3/go.mod h1:RJjV0jbUHqXLS0/Mxv5XE7LAnJHqHw+01RDdpoGqiyY= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= +github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/petstore-expanded/stdhttp/petstore.go b/examples/petstore-expanded/stdhttp/petstore.go new file mode 100644 index 0000000000..68e45cc1d7 --- /dev/null +++ b/examples/petstore-expanded/stdhttp/petstore.go @@ -0,0 +1,54 @@ +//go:build go1.22 + +// This is an example of implementing the Pet Store from the OpenAPI documentation +// found at: +// https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/petstore.yaml + +package main + +import ( + "flag" + "fmt" + "log" + "net" + "net/http" + "os" + + middleware "github.com/oapi-codegen/nethttp-middleware" + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/stdhttp/api" +) + +func main() { + port := flag.String("port", "8080", "Port for test HTTP server") + flag.Parse() + + swagger, err := api.GetSwagger() + if err != nil { + fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err) + os.Exit(1) + } + + // Clear out the servers array in the swagger spec, that skips validating + // that server names match. We don't know how this thing will be run. + swagger.Servers = nil + + // Create an instance of our handler which satisfies the generated interface + petStore := api.NewPetStore() + + r := http.NewServeMux() + + // We now register our petStore above as the handler for the interface + api.HandlerFromMux(petStore, r) + + // Use our validation middleware to check all requests against the + // OpenAPI schema. + h := middleware.OapiRequestValidator(swagger)(r) + + s := &http.Server{ + Handler: h, + Addr: net.JoinHostPort("0.0.0.0", *port), + } + + // And we serve HTTP until the world ends. + log.Fatal(s.ListenAndServe()) +} diff --git a/examples/petstore-expanded/stdhttp/petstore_test.go b/examples/petstore-expanded/stdhttp/petstore_test.go new file mode 100644 index 0000000000..bf3df022d2 --- /dev/null +++ b/examples/petstore-expanded/stdhttp/petstore_test.go @@ -0,0 +1,173 @@ +//go:build go1.22 + +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + middleware "github.com/oapi-codegen/nethttp-middleware" + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/stdhttp/api" + "github.com/oapi-codegen/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func doGet(t *testing.T, mux *http.ServeMux, url string) *httptest.ResponseRecorder { + response := testutil.NewRequest().Get(url).WithAcceptJson().GoWithHTTPHandler(t, mux) + return response.Recorder +} + +func TestPetStore(t *testing.T) { + var err error + + // Get the swagger description of our API + swagger, err := api.GetSwagger() + require.NoError(t, err) + + // Clear out the servers array in the swagger spec, that skips validating + // that server names match. We don't know how this thing will be run. + swagger.Servers = nil + + // Create a new ServeMux for testing. + m := http.NewServeMux() + + // Use our validation middleware to check all requests against the + // OpenAPI schema. + opts := api.StdHTTPServerOptions{ + BaseRouter: m, + Middlewares: []api.MiddlewareFunc{ + middleware.OapiRequestValidator(swagger), + }, + } + + store := api.NewPetStore() + api.HandlerWithOptions(store, opts) + + t.Run("Add pet", func(t *testing.T) { + tag := "TagOfSpot" + newPet := api.NewPet{ + Name: "Spot", + Tag: &tag, + } + + rr := testutil.NewRequest().Post("/pets").WithJsonBody(newPet).GoWithHTTPHandler(t, m).Recorder + assert.Equal(t, http.StatusCreated, rr.Code) + + var resultPet api.Pet + err = json.NewDecoder(rr.Body).Decode(&resultPet) + assert.NoError(t, err, "error unmarshaling response") + assert.Equal(t, newPet.Name, resultPet.Name) + assert.Equal(t, *newPet.Tag, *resultPet.Tag) + }) + + t.Run("Find pet by ID", func(t *testing.T) { + pet := api.Pet{ + Id: 100, + } + + store.Pets[pet.Id] = pet + rr := doGet(t, m, fmt.Sprintf("/pets/%d", pet.Id)) + + var resultPet api.Pet + err = json.NewDecoder(rr.Body).Decode(&resultPet) + assert.NoError(t, err, "error getting pet") + assert.Equal(t, pet, resultPet) + }) + + t.Run("Pet not found", func(t *testing.T) { + rr := doGet(t, m, "/pets/27179095781") + assert.Equal(t, http.StatusNotFound, rr.Code) + + var petError api.Error + err = json.NewDecoder(rr.Body).Decode(&petError) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, int32(http.StatusNotFound), petError.Code) + }) + + t.Run("List all pets", func(t *testing.T) { + store.Pets = map[int64]api.Pet{ + 1: {}, + 2: {}, + } + + // Now, list all pets, we should have two + rr := doGet(t, m, "/pets") + assert.Equal(t, http.StatusOK, rr.Code) + + var petList []api.Pet + err = json.NewDecoder(rr.Body).Decode(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 2, len(petList)) + }) + + t.Run("Filter pets by tag", func(t *testing.T) { + tag := "TagOfFido" + + store.Pets = map[int64]api.Pet{ + 1: { + Tag: &tag, + }, + 2: {}, + } + + // Filter pets by tag, we should have 1 + rr := doGet(t, m, "/pets?tags=TagOfFido") + assert.Equal(t, http.StatusOK, rr.Code) + + var petList []api.Pet + err = json.NewDecoder(rr.Body).Decode(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 1, len(petList)) + }) + + t.Run("Filter pets by tag", func(t *testing.T) { + store.Pets = map[int64]api.Pet{ + 1: {}, + 2: {}, + } + + // Filter pets by non-existent tag, we should have 0 + rr := doGet(t, m, "/pets?tags=NotExists") + assert.Equal(t, http.StatusOK, rr.Code) + + var petList []api.Pet + err = json.NewDecoder(rr.Body).Decode(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 0, len(petList)) + }) + + t.Run("Delete pets", func(t *testing.T) { + store.Pets = map[int64]api.Pet{ + 1: {}, + 2: {}, + } + + // Let's delete non-existent pet + rr := testutil.NewRequest().Delete("/pets/7").GoWithHTTPHandler(t, m).Recorder + assert.Equal(t, http.StatusNotFound, rr.Code) + + var petError api.Error + err = json.NewDecoder(rr.Body).Decode(&petError) + assert.NoError(t, err, "error unmarshaling PetError") + assert.Equal(t, int32(http.StatusNotFound), petError.Code) + + // Now, delete both real pets + rr = testutil.NewRequest().Delete("/pets/1").GoWithHTTPHandler(t, m).Recorder + assert.Equal(t, http.StatusNoContent, rr.Code) + + rr = testutil.NewRequest().Delete("/pets/2").GoWithHTTPHandler(t, m).Recorder + assert.Equal(t, http.StatusNoContent, rr.Code) + + // Should have no pets left. + var petList []api.Pet + rr = doGet(t, m, "/pets") + assert.Equal(t, http.StatusOK, rr.Code) + err = json.NewDecoder(rr.Body).Decode(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 0, len(petList)) + }) +} diff --git a/examples/petstore-expanded/stdhttp/tools/tools.go b/examples/petstore-expanded/stdhttp/tools/tools.go new file mode 100644 index 0000000000..8615cb4c57 --- /dev/null +++ b/examples/petstore-expanded/stdhttp/tools/tools.go @@ -0,0 +1,8 @@ +//go:build tools +// +build tools + +package tools + +import ( + _ "github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen" +) diff --git a/examples/petstore-expanded/strict/api/petstore-server.gen.go b/examples/petstore-expanded/strict/api/petstore-server.gen.go index cd9352ac11..f1ba3d6d44 100644 --- a/examples/petstore-expanded/strict/api/petstore-server.gen.go +++ b/examples/petstore-expanded/strict/api/petstore-server.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api import ( @@ -15,9 +15,10 @@ import ( "path" "strings" - "github.com/deepmap/oapi-codegen/pkg/runtime" "github.com/getkin/kin-openapi/openapi3" "github.com/go-chi/chi/v5" + "github.com/oapi-codegen/runtime" + strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp" ) // ServerInterface represents all server handlers. @@ -36,6 +37,34 @@ type ServerInterface interface { FindPetByID(w http.ResponseWriter, r *http.Request, id int64) } +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct{} + +// Returns all pets +// (GET /pets) +func (_ Unimplemented) FindPets(w http.ResponseWriter, r *http.Request, params FindPetsParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Creates a new pet +// (POST /pets) +func (_ Unimplemented) AddPet(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Deletes a pet by ID +// (DELETE /pets/{id}) +func (_ Unimplemented) DeletePet(w http.ResponseWriter, r *http.Request, id int64) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Returns a pet by ID +// (GET /pets/{id}) +func (_ Unimplemented) FindPetByID(w http.ResponseWriter, r *http.Request, id int64) { + w.WriteHeader(http.StatusNotImplemented) +} + // ServerInterfaceWrapper converts contexts to parameters. type ServerInterfaceWrapper struct { Handler ServerInterface @@ -47,7 +76,6 @@ type MiddlewareFunc func(http.Handler) http.Handler // FindPets operation middleware func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() var err error @@ -70,82 +98,79 @@ func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Reque return } - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.FindPets(w, r, params) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // AddPet operation middleware func (siw *ServerInterfaceWrapper) AddPet(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.AddPet(w, r) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // DeletePet operation middleware func (siw *ServerInterfaceWrapper) DeletePet(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() var err error // ------------- Path parameter "id" ------------- var id int64 - err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, chi.URLParam(r, "id"), &id) + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) return } - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.DeletePet(w, r, id) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // FindPetByID operation middleware func (siw *ServerInterfaceWrapper) FindPetByID(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() var err error // ------------- Path parameter "id" ------------- var id int64 - err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, chi.URLParam(r, "id"), &id) + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) return } - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.FindPetByID(w, r, id) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } type UnescapedCookieParamError struct { @@ -161,16 +186,16 @@ func (e *UnescapedCookieParamError) Unwrap() error { return e.Err } -type UnmarshallingParamError struct { +type UnmarshalingParamError struct { ParamName string Err error } -func (e *UnmarshallingParamError) Error() string { - return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) } -func (e *UnmarshallingParamError) Unwrap() error { +func (e *UnmarshalingParamError) Unwrap() error { return e.Err } @@ -408,9 +433,8 @@ type StrictServerInterface interface { FindPetByID(ctx context.Context, request FindPetByIDRequestObject) (FindPetByIDResponseObject, error) } -type StrictHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, args interface{}) (interface{}, error) - -type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc +type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc +type StrictMiddlewareFunc = strictnethttp.StrictHTTPMiddlewareFunc type StrictHTTPServerOptions struct { RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) @@ -460,7 +484,7 @@ func (sh *strictHandler) FindPets(w http.ResponseWriter, r *http.Request, params sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) } } @@ -491,7 +515,7 @@ func (sh *strictHandler) AddPet(w http.ResponseWriter, r *http.Request) { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) } } @@ -517,7 +541,7 @@ func (sh *strictHandler) DeletePet(w http.ResponseWriter, r *http.Request, id in sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) } } @@ -543,7 +567,7 @@ func (sh *strictHandler) FindPetByID(w http.ResponseWriter, r *http.Request, id sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) } } @@ -585,16 +609,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -612,7 +636,7 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } @@ -626,12 +650,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/examples/petstore-expanded/strict/api/petstore-types.gen.go b/examples/petstore-expanded/strict/api/petstore-types.gen.go index 16bbe200cf..0e4954f349 100644 --- a/examples/petstore-expanded/strict/api/petstore-types.gen.go +++ b/examples/petstore-expanded/strict/api/petstore-types.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api // Error defines model for Error. diff --git a/examples/petstore-expanded/strict/api/petstore.go b/examples/petstore-expanded/strict/api/petstore.go index dc2c3016fd..fc5c9fda2f 100644 --- a/examples/petstore-expanded/strict/api/petstore.go +++ b/examples/petstore-expanded/strict/api/petstore.go @@ -1,5 +1,5 @@ -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=types.cfg.yaml ../../petstore-expanded.yaml -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=server.cfg.yaml ../../petstore-expanded.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=types.cfg.yaml ../../petstore-expanded.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=server.cfg.yaml ../../petstore-expanded.yaml package api @@ -70,7 +70,7 @@ func (p *PetStore) AddPet(ctx context.Context, request AddPetRequestObject) (Add pet.Name = request.Body.Name pet.Tag = request.Body.Tag pet.Id = p.NextId - p.NextId = p.NextId + 1 + p.NextId++ // Insert into map p.Pets[pet.Id] = pet diff --git a/examples/petstore-expanded/strict/api/server.cfg.yaml b/examples/petstore-expanded/strict/api/server.cfg.yaml index f3c71c63f5..48d2bf90b3 100644 --- a/examples/petstore-expanded/strict/api/server.cfg.yaml +++ b/examples/petstore-expanded/strict/api/server.cfg.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: api generate: chi-server: true diff --git a/examples/petstore-expanded/strict/api/types.cfg.yaml b/examples/petstore-expanded/strict/api/types.cfg.yaml index 9ac30e11e9..cf5128b726 100644 --- a/examples/petstore-expanded/strict/api/types.cfg.yaml +++ b/examples/petstore-expanded/strict/api/types.cfg.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: api generate: models: true diff --git a/examples/petstore-expanded/strict/petstore.go b/examples/petstore-expanded/strict/petstore.go index c34860034e..363caf7e1a 100644 --- a/examples/petstore-expanded/strict/petstore.go +++ b/examples/petstore-expanded/strict/petstore.go @@ -8,16 +8,17 @@ import ( "flag" "fmt" "log" + "net" "net/http" "os" - "github.com/deepmap/oapi-codegen/examples/petstore-expanded/strict/api" - middleware "github.com/deepmap/oapi-codegen/pkg/chi-middleware" "github.com/go-chi/chi/v5" + middleware "github.com/oapi-codegen/nethttp-middleware" + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/strict/api" ) func main() { - var port = flag.Int("port", 8080, "Port for test HTTP server") + port := flag.String("port", "8080", "Port for test HTTP server") flag.Parse() swagger, err := api.GetSwagger() @@ -47,7 +48,7 @@ func main() { s := &http.Server{ Handler: r, - Addr: fmt.Sprintf("0.0.0.0:%d", *port), + Addr: net.JoinHostPort("0.0.0.0", *port), } // And we serve HTTP until the world ends. diff --git a/examples/petstore-expanded/strict/petstore_test.go b/examples/petstore-expanded/strict/petstore_test.go index 4cbfee02a3..208b65ea3e 100644 --- a/examples/petstore-expanded/strict/petstore_test.go +++ b/examples/petstore-expanded/strict/petstore_test.go @@ -7,10 +7,10 @@ import ( "net/http/httptest" "testing" - "github.com/deepmap/oapi-codegen/examples/petstore-expanded/strict/api" - middleware "github.com/deepmap/oapi-codegen/pkg/chi-middleware" - "github.com/deepmap/oapi-codegen/pkg/testutil" "github.com/go-chi/chi/v5" + middleware "github.com/oapi-codegen/nethttp-middleware" + "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded/strict/api" + "github.com/oapi-codegen/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -53,7 +53,7 @@ func TestPetStore(t *testing.T) { var resultPet api.Pet err = json.NewDecoder(rr.Body).Decode(&resultPet) - assert.NoError(t, err, "error unmarshalling response") + assert.NoError(t, err, "error unmarshaling response") assert.Equal(t, newPet.Name, resultPet.Name) assert.Equal(t, *newPet.Tag, *resultPet.Tag) }) @@ -146,7 +146,7 @@ func TestPetStore(t *testing.T) { var petError api.Error err = json.NewDecoder(rr.Body).Decode(&petError) - assert.NoError(t, err, "error unmarshalling PetError") + assert.NoError(t, err, "error unmarshaling PetError") assert.Equal(t, int32(http.StatusNotFound), petError.Code) // Now, delete both real pets diff --git a/examples/tools.go b/examples/tools.go new file mode 100644 index 0000000000..8615cb4c57 --- /dev/null +++ b/examples/tools.go @@ -0,0 +1,8 @@ +//go:build tools +// +build tools + +package tools + +import ( + _ "github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen" +) diff --git a/go.mod b/go.mod index afa69bc3bf..ce84f98d4e 100644 --- a/go.mod +++ b/go.mod @@ -1,65 +1,35 @@ -module github.com/deepmap/oapi-codegen +module github.com/oapi-codegen/oapi-codegen/v2 + +go 1.24.3 + +toolchain go1.24.4 require ( - github.com/apapsch/go-jsonmerge/v2 v2.0.0 - github.com/getkin/kin-openapi v0.114.0 - github.com/gin-gonic/gin v1.8.2 - github.com/go-chi/chi/v5 v5.0.8 - github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 - github.com/google/uuid v1.3.0 - github.com/gorilla/mux v1.8.0 - github.com/labstack/echo/v4 v4.10.2 - github.com/lestrrat-go/jwx v1.2.25 - github.com/matryer/moq v0.3.0 - github.com/stretchr/testify v1.8.2 - golang.org/x/text v0.8.0 - golang.org/x/tools v0.6.0 + github.com/getkin/kin-openapi v0.133.0 + github.com/speakeasy-api/openapi-overlay v0.10.2 + github.com/stretchr/testify v1.11.1 + golang.org/x/mod v0.33.0 + golang.org/x/text v0.34.0 + golang.org/x/tools v0.42.0 gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/swag v0.21.1 // indirect - github.com/go-playground/locales v0.14.0 // indirect - github.com/go-playground/universal-translator v0.18.0 // indirect - github.com/go-playground/validator/v10 v10.11.1 // indirect - github.com/goccy/go-json v0.9.11 // indirect - github.com/golang-jwt/jwt v3.2.2+incompatible // indirect - github.com/invopop/yaml v0.1.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/labstack/gommon v0.4.0 // indirect - github.com/leodido/go-urn v1.2.1 // indirect - github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect - github.com/lestrrat-go/blackmagic v1.0.0 // indirect - github.com/lestrrat-go/httpcc v1.0.1 // indirect - github.com/lestrrat-go/iter v1.0.1 // indirect - github.com/lestrrat-go/option v1.0.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect - github.com/pelletier/go-toml/v2 v2.0.6 // indirect - github.com/perimeterx/marshmallow v1.1.4 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/objx v0.5.0 // indirect - github.com/ugorji/go/codec v1.2.7 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.2 // indirect - golang.org/x/crypto v0.6.0 // indirect - golang.org/x/mod v0.8.0 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect - google.golang.org/protobuf v1.28.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/speakeasy-api/jsonpath v0.6.0 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect + golang.org/x/sync v0.19.0 // indirect ) - -go 1.18 diff --git a/go.sum b/go.sum index 81809fbd8b..8e99a7a7fd 100644 --- a/go.sum +++ b/go.sum @@ -1,190 +1,172 @@ -github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= -github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= -github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= -github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/getkin/kin-openapi v0.114.0 h1:ar7QiJpDdlR+zSyPjrLf8mNnpoFP/lI90XcywMCFNe8= -github.com/getkin/kin-openapi v0.114.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.8.2 h1:UzKToD9/PoFj/V4rvlKqTRKnQYyz8Sc1MJlv4JHPtvY= -github.com/gin-gonic/gin v1.8.2/go.mod h1:qw5AYuDrzRTnhvusDsrov+fDIxp9Dleuu12h8nfB398= -github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= -github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= -github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= -github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= -github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 h1:utua3L2IbQJmauC5IXdEA547bcoU5dozgQAfc8Onsg4= -github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc= -github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= -github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= -github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= -github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= -github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= -github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4= -github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= -github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= -github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A= -github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= -github.com/lestrrat-go/jwx v1.2.25 h1:tAx93jN2SdPvFn08fHNAhqFJazn5mBBOB8Zli0g0otA= -github.com/lestrrat-go/jwx v1.2.25/go.mod h1:zoNuZymNl5lgdcu6P7K6ie2QRll5HVfF4xwxBBK1NxY= -github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= -github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/matryer/moq v0.3.0 h1:4j0goF/XK3pMTc7fJB3fveuTJoQNdavRX/78vlK3Xb4= -github.com/matryer/moq v0.3.0/go.mod h1:RJ75ZZZD71hejp39j4crZLsEDszGk6iH4v4YsWFKH4s= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= -github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= -github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw= -github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8= +github.com/speakeasy-api/jsonpath v0.6.0/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw= +github.com/speakeasy-api/openapi-overlay v0.10.2 h1:VOdQ03eGKeiHnpb1boZCGm7x8Haj6gST0P3SGTX95GU= +github.com/speakeasy-api/openapi-overlay v0.10.2/go.mod h1:n0iOU7AqKpNFfEt6tq7qYITC4f0yzVVdFw0S7hukemg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= -github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= -github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= -github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= +github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/greptile.json b/greptile.json new file mode 100644 index 0000000000..bc3cc79fc0 --- /dev/null +++ b/greptile.json @@ -0,0 +1,4 @@ +{ + "skipReview": "AUTOMATIC", + "ignorePatterns": "**/*.gen.go" +} diff --git a/internal/test/Makefile b/internal/test/Makefile new file mode 100644 index 0000000000..5ec0edd058 --- /dev/null +++ b/internal/test/Makefile @@ -0,0 +1,17 @@ +lint: + $(GOBIN)/golangci-lint run ./... + +lint-ci: + $(GOBIN)/golangci-lint run ./... --output.text.path=stdout --timeout=5m + +generate: + go generate ./... + +test: + go test -cover ./... + +tidy: + go mod tidy + +tidy-ci: + tidied -verbose diff --git a/internal/test/all_of/config1.yaml b/internal/test/all_of/config1.yaml index 5e023c1f47..1cc8fe87c7 100644 --- a/internal/test/all_of/config1.yaml +++ b/internal/test/all_of/config1.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../configuration-schema.json package: v1 generate: models: true diff --git a/internal/test/all_of/config2.yaml b/internal/test/all_of/config2.yaml index bd93f3c391..b5b9d64be7 100644 --- a/internal/test/all_of/config2.yaml +++ b/internal/test/all_of/config2.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../configuration-schema.json package: v2 generate: models: true diff --git a/internal/test/all_of/doc.go b/internal/test/all_of/doc.go index 0388e17ebf..b154cb2245 100644 --- a/internal/test/all_of/doc.go +++ b/internal/test/all_of/doc.go @@ -1,4 +1,4 @@ package allof -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config1.yaml openapi.yaml -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config2.yaml openapi.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config1.yaml openapi.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config2.yaml openapi.yaml diff --git a/internal/test/all_of/v1/openapi.gen.go b/internal/test/all_of/v1/openapi.gen.go index 0023261252..db5fd302cd 100644 --- a/internal/test/all_of/v1/openapi.gen.go +++ b/internal/test/all_of/v1/openapi.gen.go @@ -1,6 +1,6 @@ // Package v1 provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package v1 import ( @@ -59,16 +59,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -86,7 +86,7 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } @@ -100,12 +100,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/internal/test/all_of/v2/openapi.gen.go b/internal/test/all_of/v2/openapi.gen.go index 3222dcf0a6..ab430340d6 100644 --- a/internal/test/all_of/v2/openapi.gen.go +++ b/internal/test/all_of/v2/openapi.gen.go @@ -1,6 +1,6 @@ // Package v2 provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package v2 import ( @@ -59,16 +59,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -86,7 +86,7 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } @@ -100,12 +100,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/internal/test/any_of/codegen/inline/config.yaml b/internal/test/any_of/codegen/inline/config.yaml new file mode 100644 index 0000000000..9066c3fa38 --- /dev/null +++ b/internal/test/any_of/codegen/inline/config.yaml @@ -0,0 +1,6 @@ +package: inline +generate: + models: true + client: true + echo-server: true +output: openapi.gen.go diff --git a/internal/test/any_of/codegen/inline/generate.go b/internal/test/any_of/codegen/inline/generate.go new file mode 100644 index 0000000000..020b7dc9d8 --- /dev/null +++ b/internal/test/any_of/codegen/inline/generate.go @@ -0,0 +1,3 @@ +package inline + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/any_of/codegen/inline/openapi.gen.go b/internal/test/any_of/codegen/inline/openapi.gen.go new file mode 100644 index 0000000000..d3f34bec6e --- /dev/null +++ b/internal/test/any_of/codegen/inline/openapi.gen.go @@ -0,0 +1,328 @@ +// Package inline provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package inline + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/labstack/echo/v4" +) + +const ( + ApiKeyAuthScopes = "ApiKeyAuth.Scopes" +) + +// Cat This is a cat +type Cat struct { + Breed *string `json:"breed,omitempty"` + Color *string `json:"color,omitempty"` + Id *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Purrs *bool `json:"purrs,omitempty"` +} + +// Dog This is a dog +type Dog struct { + Barks *bool `json:"barks,omitempty"` + Breed *string `json:"breed,omitempty"` + Color *string `json:"color,omitempty"` + Id *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` +} + +// Rat This is a rat +type Rat struct { + Color *string `json:"color,omitempty"` + Id *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Squeaks *bool `json:"squeaks,omitempty"` +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetPets request + GetPets(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) GetPets(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetPetsRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetPetsRequest generates requests for GetPets +func NewGetPetsRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/pets") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetPetsWithResponse request + GetPetsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetPetsResponse, error) +} + +type GetPetsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *struct { + Data *[]GetPets_200_Data_Item `json:"data,omitempty"` + } +} +type GetPets_200_Data_Item struct { + union json.RawMessage +} + +// Status returns HTTPResponse.Status +func (r GetPetsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetPetsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// GetPetsWithResponse request returning *GetPetsResponse +func (c *ClientWithResponses) GetPetsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetPetsResponse, error) { + rsp, err := c.GetPets(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetPetsResponse(rsp) +} + +// ParseGetPetsResponse parses an HTTP response from a GetPetsWithResponse call +func ParseGetPetsResponse(rsp *http.Response) (*GetPetsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetPetsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest struct { + Data *[]GetPets_200_Data_Item `json:"data,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Get a list of pets + // (GET /pets) + GetPets(ctx echo.Context) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// GetPets converts echo context to params. +func (w *ServerInterfaceWrapper) GetPets(ctx echo.Context) error { + var err error + + ctx.Set(ApiKeyAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetPets(ctx) + return err +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.GET(baseURL+"/pets", wrapper.GetPets) + +} diff --git a/internal/test/any_of/codegen/inline/spec.yaml b/internal/test/any_of/codegen/inline/spec.yaml new file mode 100644 index 0000000000..bb0e44448c --- /dev/null +++ b/internal/test/any_of/codegen/inline/spec.yaml @@ -0,0 +1,81 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: Cats, Dogs and Rats API + description: This API allows the client to receive information about cats, dogs and rats. +servers: + - url: https://example.com/api +security: + - ApiKeyAuth: [] +paths: + /pets: + get: + summary: Get a list of pets + description: This endpoint returns a list of pets. Each pet can be either a cat, dog or a rat. + operationId: getPets + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + anyOf: + - $ref: '#/components/schemas/Cat' + - $ref: '#/components/schemas/Dog' + - $ref: '#/components/schemas/Rat' + '401': + description: Unauthorized + '500': + description: Internal Server Error +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + schemas: + Cat: + type: object + description: This is a cat + properties: + id: + type: string + name: + type: string + breed: + type: string + color: + type: string + purrs: + type: boolean + Dog: + type: object + description: This is a dog + properties: + id: + type: string + name: + type: string + breed: + type: string + color: + type: string + barks: + type: boolean + Rat: + type: object + description: This is a rat + properties: + id: + type: string + name: + type: string + color: + type: string + squeaks: + type: boolean diff --git a/internal/test/any_of/codegen/ref_schema/config.yaml b/internal/test/any_of/codegen/ref_schema/config.yaml new file mode 100644 index 0000000000..07176c2906 --- /dev/null +++ b/internal/test/any_of/codegen/ref_schema/config.yaml @@ -0,0 +1,6 @@ +package: ref_schema +generate: + models: true + client: true + echo-server: true +output: openapi.gen.go diff --git a/internal/test/any_of/codegen/ref_schema/generate.go b/internal/test/any_of/codegen/ref_schema/generate.go new file mode 100644 index 0000000000..061e2a828d --- /dev/null +++ b/internal/test/any_of/codegen/ref_schema/generate.go @@ -0,0 +1,3 @@ +package ref_schema + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/any_of/codegen/ref_schema/openapi.gen.go b/internal/test/any_of/codegen/ref_schema/openapi.gen.go new file mode 100644 index 0000000000..38f87c47be --- /dev/null +++ b/internal/test/any_of/codegen/ref_schema/openapi.gen.go @@ -0,0 +1,420 @@ +// Package ref_schema provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package ref_schema + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/labstack/echo/v4" + "github.com/oapi-codegen/runtime" +) + +const ( + ApiKeyAuthScopes = "ApiKeyAuth.Scopes" +) + +// Cat This is a cat +type Cat struct { + Breed *string `json:"breed,omitempty"` + Color *string `json:"color,omitempty"` + Id *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Purrs *bool `json:"purrs,omitempty"` +} + +// Dog This is a dog +type Dog struct { + Barks *bool `json:"barks,omitempty"` + Breed *string `json:"breed,omitempty"` + Color *string `json:"color,omitempty"` + Id *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` +} + +// GetPetsDto defines model for GetPetsDto. +type GetPetsDto struct { + Data *GetPetsDto_Data `json:"data,omitempty"` +} + +// GetPetsDto_Data defines model for GetPetsDto.Data. +type GetPetsDto_Data struct { + union json.RawMessage +} + +// Rat This is a rat +type Rat struct { + Color *string `json:"color,omitempty"` + Id *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Squeaks *bool `json:"squeaks,omitempty"` +} + +// AsCat returns the union data inside the GetPetsDto_Data as a Cat +func (t GetPetsDto_Data) AsCat() (Cat, error) { + var body Cat + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromCat overwrites any union data inside the GetPetsDto_Data as the provided Cat +func (t *GetPetsDto_Data) FromCat(v Cat) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeCat performs a merge with any union data inside the GetPetsDto_Data, using the provided Cat +func (t *GetPetsDto_Data) MergeCat(v Cat) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsDog returns the union data inside the GetPetsDto_Data as a Dog +func (t GetPetsDto_Data) AsDog() (Dog, error) { + var body Dog + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromDog overwrites any union data inside the GetPetsDto_Data as the provided Dog +func (t *GetPetsDto_Data) FromDog(v Dog) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeDog performs a merge with any union data inside the GetPetsDto_Data, using the provided Dog +func (t *GetPetsDto_Data) MergeDog(v Dog) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsRat returns the union data inside the GetPetsDto_Data as a Rat +func (t GetPetsDto_Data) AsRat() (Rat, error) { + var body Rat + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromRat overwrites any union data inside the GetPetsDto_Data as the provided Rat +func (t *GetPetsDto_Data) FromRat(v Rat) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeRat performs a merge with any union data inside the GetPetsDto_Data, using the provided Rat +func (t *GetPetsDto_Data) MergeRat(v Rat) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t GetPetsDto_Data) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *GetPetsDto_Data) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetPets request + GetPets(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) GetPets(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetPetsRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetPetsRequest generates requests for GetPets +func NewGetPetsRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/pets") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetPetsWithResponse request + GetPetsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetPetsResponse, error) +} + +type GetPetsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *GetPetsDto +} + +// Status returns HTTPResponse.Status +func (r GetPetsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetPetsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// GetPetsWithResponse request returning *GetPetsResponse +func (c *ClientWithResponses) GetPetsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetPetsResponse, error) { + rsp, err := c.GetPets(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetPetsResponse(rsp) +} + +// ParseGetPetsResponse parses an HTTP response from a GetPetsWithResponse call +func ParseGetPetsResponse(rsp *http.Response) (*GetPetsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetPetsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest GetPetsDto + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Get a list of pets + // (GET /pets) + GetPets(ctx echo.Context) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// GetPets converts echo context to params. +func (w *ServerInterfaceWrapper) GetPets(ctx echo.Context) error { + var err error + + ctx.Set(ApiKeyAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetPets(ctx) + return err +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.GET(baseURL+"/pets", wrapper.GetPets) + +} diff --git a/internal/test/any_of/codegen/ref_schema/spec.yaml b/internal/test/any_of/codegen/ref_schema/spec.yaml new file mode 100644 index 0000000000..f571db0134 --- /dev/null +++ b/internal/test/any_of/codegen/ref_schema/spec.yaml @@ -0,0 +1,81 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: Cats, Dogs and Rats API + description: This API allows the client to receive information about cats, dogs and rats. +servers: + - url: https://example.com/api +security: + - ApiKeyAuth: [] +paths: + /pets: + get: + summary: Get a list of pets + description: This endpoint returns a list of pets. Each pet can be either a cat, dog or a rat. + operationId: getPets + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/GetPetsDto' + '401': + description: Unauthorized + '500': + description: Internal Server Error +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + schemas: + GetPetsDto: + type: object + properties: + data: + anyOf: + - $ref: '#/components/schemas/Cat' + - $ref: '#/components/schemas/Dog' + - $ref: '#/components/schemas/Rat' + Cat: + type: object + description: This is a cat + properties: + id: + type: string + name: + type: string + breed: + type: string + color: + type: string + purrs: + type: boolean + Dog: + type: object + description: This is a dog + properties: + id: + type: string + name: + type: string + breed: + type: string + color: + type: string + barks: + type: boolean + Rat: + type: object + description: This is a rat + properties: + id: + type: string + name: + type: string + color: + type: string + squeaks: + type: boolean diff --git a/internal/test/any_of/param/config.yaml b/internal/test/any_of/param/config.yaml index 7b2827042a..1e35ec88ba 100644 --- a/internal/test/any_of/param/config.yaml +++ b/internal/test/any_of/param/config.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: param generate: models: true diff --git a/internal/test/any_of/param/doc.go b/internal/test/any_of/param/doc.go index 63ff22389a..657385e03f 100644 --- a/internal/test/any_of/param/doc.go +++ b/internal/test/any_of/param/doc.go @@ -1,3 +1,3 @@ package param -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml spec.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/any_of/param/param.gen.go b/internal/test/any_of/param/param.gen.go index 7de9459481..483228d045 100644 --- a/internal/test/any_of/param/param.gen.go +++ b/internal/test/any_of/param/param.gen.go @@ -1,6 +1,6 @@ // Package param provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package param import ( @@ -12,7 +12,7 @@ import ( "net/url" "strings" - "github.com/deepmap/oapi-codegen/pkg/runtime" + "github.com/oapi-codegen/runtime" ) // Test defines model for test. @@ -70,7 +70,7 @@ func (t *Test) MergeTest0(v Test0) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -96,7 +96,7 @@ func (t *Test) MergeTest1(v Test1) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -132,7 +132,7 @@ func (t *Test2) MergeTest20(v Test20) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -158,7 +158,7 @@ func (t *Test2) MergeTest21(v Test21) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -281,42 +281,44 @@ func NewGetTestRequest(server string, params *GetTestParams) (*http.Request, err return nil, err } - queryValues := queryURL.Query() + if params != nil { + queryValues := queryURL.Query() - if params.Test != nil { + if params.Test != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "test", runtime.ParamLocationQuery, *params.Test); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "test", *params.Test, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "", Format: ""}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } - } - } - - if params.Test2 != nil { + } - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "test2", runtime.ParamLocationQuery, *params.Test2); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if params.Test2 != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "test2", *params.Test2, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "array", Format: ""}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } + } + queryURL.RawQuery = queryValues.Encode() } - queryURL.RawQuery = queryValues.Encode() - req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -368,7 +370,7 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { - // GetTest request + // GetTestWithResponse request GetTestWithResponse(ctx context.Context, params *GetTestParams, reqEditors ...RequestEditorFn) (*GetTestResponse, error) } diff --git a/internal/test/any_of/param/param_test.go b/internal/test/any_of/param/param_test.go index 748ebc54e2..94641b3650 100644 --- a/internal/test/any_of/param/param_test.go +++ b/internal/test/any_of/param/param_test.go @@ -3,7 +3,7 @@ package param_test import ( "testing" - "github.com/deepmap/oapi-codegen/internal/test/any_of/param" + "github.com/oapi-codegen/oapi-codegen/v2/internal/test/any_of/param" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/internal/test/client/client.gen.go b/internal/test/client/client.gen.go index 865727b6ac..275c72cf0f 100644 --- a/internal/test/client/client.gen.go +++ b/internal/test/client/client.gen.go @@ -1,6 +1,6 @@ // Package client provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package client import ( @@ -24,8 +24,8 @@ type SchemaObject struct { Role string `json:"role"` } -// PostVendorJsonJSONBody defines parameters for PostVendorJson. -type PostVendorJsonJSONBody = map[string]interface{} +// PostVendorJsonApplicationVndAPIPlusJSONBody defines parameters for PostVendorJson. +type PostVendorJsonApplicationVndAPIPlusJSONBody = map[string]interface{} // PostBothJSONRequestBody defines body for PostBoth for application/json ContentType. type PostBothJSONRequestBody = SchemaObject @@ -33,8 +33,8 @@ type PostBothJSONRequestBody = SchemaObject // PostJsonJSONRequestBody defines body for PostJson for application/json ContentType. type PostJsonJSONRequestBody = SchemaObject -// PostVendorJsonJSONRequestBody defines body for PostVendorJson for application/vnd.api+json ContentType. -type PostVendorJsonJSONRequestBody = PostVendorJsonJSONBody +// PostVendorJsonApplicationVndAPIPlusJSONRequestBody defines body for PostVendorJson for application/vnd.api+json ContentType. +type PostVendorJsonApplicationVndAPIPlusJSONRequestBody = PostVendorJsonApplicationVndAPIPlusJSONBody // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -109,7 +109,7 @@ func WithRequestEditorFn(fn RequestEditorFn) ClientOption { // The interface specification for the client above. type ClientInterface interface { - // PostBoth request with any body + // PostBothWithBody request with any body PostBothWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) PostBoth(ctx context.Context, body PostBothJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -117,7 +117,7 @@ type ClientInterface interface { // GetBoth request GetBoth(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - // PostJson request with any body + // PostJsonWithBody request with any body PostJsonWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) PostJson(ctx context.Context, body PostJsonJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -125,7 +125,7 @@ type ClientInterface interface { // GetJson request GetJson(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - // PostOther request with any body + // PostOtherWithBody request with any body PostOtherWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) // GetOther request @@ -134,10 +134,10 @@ type ClientInterface interface { // GetJsonWithTrailingSlash request GetJsonWithTrailingSlash(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - // PostVendorJson request with any body + // PostVendorJsonWithBody request with any body PostVendorJsonWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PostVendorJson(ctx context.Context, body PostVendorJsonJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PostVendorJsonWithApplicationVndAPIPlusJSONBody(ctx context.Context, body PostVendorJsonApplicationVndAPIPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) } func (c *Client) PostBothWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -260,8 +260,8 @@ func (c *Client) PostVendorJsonWithBody(ctx context.Context, contentType string, return c.Client.Do(req) } -func (c *Client) PostVendorJson(ctx context.Context, body PostVendorJsonJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostVendorJsonRequest(c.Server, body) +func (c *Client) PostVendorJsonWithApplicationVndAPIPlusJSONBody(ctx context.Context, body PostVendorJsonApplicationVndAPIPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostVendorJsonRequestWithApplicationVndAPIPlusJSONBody(c.Server, body) if err != nil { return nil, err } @@ -489,8 +489,8 @@ func NewGetJsonWithTrailingSlashRequest(server string) (*http.Request, error) { return req, nil } -// NewPostVendorJsonRequest calls the generic PostVendorJson builder with application/vnd.api+json body -func NewPostVendorJsonRequest(server string, body PostVendorJsonJSONRequestBody) (*http.Request, error) { +// NewPostVendorJsonRequestWithApplicationVndAPIPlusJSONBody calls the generic PostVendorJson builder with application/vnd.api+json body +func NewPostVendorJsonRequestWithApplicationVndAPIPlusJSONBody(server string, body PostVendorJsonApplicationVndAPIPlusJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { @@ -572,35 +572,35 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { - // PostBoth request with any body + // PostBothWithBodyWithResponse request with any body PostBothWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostBothResponse, error) PostBothWithResponse(ctx context.Context, body PostBothJSONRequestBody, reqEditors ...RequestEditorFn) (*PostBothResponse, error) - // GetBoth request + // GetBothWithResponse request GetBothWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetBothResponse, error) - // PostJson request with any body + // PostJsonWithBodyWithResponse request with any body PostJsonWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostJsonResponse, error) PostJsonWithResponse(ctx context.Context, body PostJsonJSONRequestBody, reqEditors ...RequestEditorFn) (*PostJsonResponse, error) - // GetJson request + // GetJsonWithResponse request GetJsonWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetJsonResponse, error) - // PostOther request with any body + // PostOtherWithBodyWithResponse request with any body PostOtherWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostOtherResponse, error) - // GetOther request + // GetOtherWithResponse request GetOtherWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetOtherResponse, error) - // GetJsonWithTrailingSlash request + // GetJsonWithTrailingSlashWithResponse request GetJsonWithTrailingSlashWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetJsonWithTrailingSlashResponse, error) - // PostVendorJson request with any body + // PostVendorJsonWithBodyWithResponse request with any body PostVendorJsonWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostVendorJsonResponse, error) - PostVendorJsonWithResponse(ctx context.Context, body PostVendorJsonJSONRequestBody, reqEditors ...RequestEditorFn) (*PostVendorJsonResponse, error) + PostVendorJsonWithApplicationVndAPIPlusJSONBodyWithResponse(ctx context.Context, body PostVendorJsonApplicationVndAPIPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*PostVendorJsonResponse, error) } type PostBothResponse struct { @@ -859,8 +859,8 @@ func (c *ClientWithResponses) PostVendorJsonWithBodyWithResponse(ctx context.Con return ParsePostVendorJsonResponse(rsp) } -func (c *ClientWithResponses) PostVendorJsonWithResponse(ctx context.Context, body PostVendorJsonJSONRequestBody, reqEditors ...RequestEditorFn) (*PostVendorJsonResponse, error) { - rsp, err := c.PostVendorJson(ctx, body, reqEditors...) +func (c *ClientWithResponses) PostVendorJsonWithApplicationVndAPIPlusJSONBodyWithResponse(ctx context.Context, body PostVendorJsonApplicationVndAPIPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*PostVendorJsonResponse, error) { + rsp, err := c.PostVendorJsonWithApplicationVndAPIPlusJSONBody(ctx, body, reqEditors...) if err != nil { return nil, err } diff --git a/internal/test/client/client_test.go b/internal/test/client/client_test.go index da499f5013..ef7c5df169 100644 --- a/internal/test/client/client_test.go +++ b/internal/test/client/client_test.go @@ -3,9 +3,14 @@ package client import ( "testing" + "github.com/oapi-codegen/oapi-codegen/v2/pkg/securityprovider" "github.com/stretchr/testify/assert" ) +var ( + withTrailingSlash = "https://my-api.com/some-base-url/v1/" +) + func TestTemp(t *testing.T) { var ( @@ -42,3 +47,33 @@ func TestTemp(t *testing.T) { assert.Equal(t, expectedURL, client3.Server) assert.Equal(t, expectedURL, client4.Server) } + +func TestSecurityProviders(t *testing.T) { + bearer, err := securityprovider.NewSecurityProviderBearerToken("mytoken") + assert.NoError(t, err) + client1, err := NewClient( + withTrailingSlash, + WithRequestEditorFn(bearer.Intercept), + ) + assert.NoError(t, err) + + apiKey, err := securityprovider.NewSecurityProviderApiKey("cookie", "apikey", "mykey") + assert.NoError(t, err) + client2, err := NewClient( + withTrailingSlash, + WithRequestEditorFn(apiKey.Intercept), + ) + assert.NoError(t, err) + + basicAuth, err := securityprovider.NewSecurityProviderBasicAuth("username", "password") + assert.NoError(t, err) + client3, err := NewClient( + withTrailingSlash, + WithRequestEditorFn(basicAuth.Intercept), + ) + assert.NoError(t, err) + + assert.Equal(t, withTrailingSlash, client1.Server) + assert.Equal(t, withTrailingSlash, client2.Server) + assert.Equal(t, withTrailingSlash, client3.Server) +} diff --git a/internal/test/client/doc.go b/internal/test/client/doc.go index e7a3a106d8..930039dadd 100644 --- a/internal/test/client/doc.go +++ b/internal/test/client/doc.go @@ -1,3 +1,3 @@ package client -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=client --generate=client,types -o client.gen.go client.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --package=client --generate=client,types -o client.gen.go client.yaml diff --git a/internal/test/compatibility/preserve-original-operation-id-casing-in-embedded-spec/api.yaml b/internal/test/compatibility/preserve-original-operation-id-casing-in-embedded-spec/api.yaml new file mode 100644 index 0000000000..ace3dbd4b9 --- /dev/null +++ b/internal/test/compatibility/preserve-original-operation-id-casing-in-embedded-spec/api.yaml @@ -0,0 +1,24 @@ +openapi: "3.0.0" +info: + title: "my spec" + version: 1.0.0 +paths: + /pet: + get: + operationId: getPet + responses: + 200: + content: + application/json: + schema: + type: string + delete: + # Via https://spec.openapis.org/oas/v3.0.3.html + # operationId: Unique string used to identify the operation. The id MUST be unique among all operations described in the API. The operationId value is case-sensitive. Tools and libraries MAY use the operationId to uniquely identify an operation, therefore, it is RECOMMENDED to follow common programming naming conventions. + operationId: this-is-a-kebabAndCamel_SNAKE + responses: + 200: + content: + application/json: + schema: + type: string diff --git a/internal/test/compatibility/preserve-original-operation-id-casing-in-embedded-spec/cfg.yaml b/internal/test/compatibility/preserve-original-operation-id-casing-in-embedded-spec/cfg.yaml new file mode 100644 index 0000000000..055e75c884 --- /dev/null +++ b/internal/test/compatibility/preserve-original-operation-id-casing-in-embedded-spec/cfg.yaml @@ -0,0 +1,9 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: preserveoriginaloperationidcasinginembeddedspec +output: spec.gen.go +generate: + embedded-spec: true +output-options: + skip-prune: false +compatibility: + preserve-original-operation-id-casing-in-embedded-spec: true diff --git a/internal/test/compatibility/preserve-original-operation-id-casing-in-embedded-spec/generate.go b/internal/test/compatibility/preserve-original-operation-id-casing-in-embedded-spec/generate.go new file mode 100644 index 0000000000..5a48f1eece --- /dev/null +++ b/internal/test/compatibility/preserve-original-operation-id-casing-in-embedded-spec/generate.go @@ -0,0 +1,3 @@ +package preserveoriginaloperationidcasinginembeddedspec + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/internal/test/compatibility/preserve-original-operation-id-casing-in-embedded-spec/spec.gen.go b/internal/test/compatibility/preserve-original-operation-id-casing-in-embedded-spec/spec.gen.go new file mode 100644 index 0000000000..030c8277d9 --- /dev/null +++ b/internal/test/compatibility/preserve-original-operation-id-casing-in-embedded-spec/spec.gen.go @@ -0,0 +1,97 @@ +// Package preserveoriginaloperationidcasinginembeddedspec provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package preserveoriginaloperationidcasinginembeddedspec + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" +) + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/7SOwUrFQAxFf0Xuevpe1d3sHuJCBBH8AJnXxjbaZkIThFLm32XGlR9gNjcEcu45wPKR", + "EQ84+0KIWPcbUxoQ8E2bcRZE3J76U48SkJUkKSPivp0CNPls9f+s5DVHWsipbllpS85ZnkZE+MzWsXWp", + "+6Jrul5kfEgrLe9vL5fnRwRsZJrFqMHu+r7GkMVJGjapLjw03PnTqtUBG2ZaU5PftbqbbywTSpuA6dfo", + "r8dE/kr+H4WllJ8AAAD//+CJBDdPAQAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/compatibility/preserve-original-operation-id-casing-in-embedded-spec/spec_test.go b/internal/test/compatibility/preserve-original-operation-id-casing-in-embedded-spec/spec_test.go new file mode 100644 index 0000000000..2f395b5776 --- /dev/null +++ b/internal/test/compatibility/preserve-original-operation-id-casing-in-embedded-spec/spec_test.go @@ -0,0 +1,29 @@ +package preserveoriginaloperationidcasinginembeddedspec + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSpecReturnsOperationIdAsOriginallySpecified(t *testing.T) { + spec, err := GetSwagger() + require.NoError(t, err) + + path := spec.Paths.Find("/pet") + require.NotNil(t, path, "The path /pet could not be found") + + operation := path.GetOperation(http.MethodGet) + require.NotNil(t, operation, "The GET operation on the path /pet could not be found") + + // this should be the raw operationId from the spec + assert.Equal(t, "getPet", operation.OperationID) + + operation = path.GetOperation(http.MethodDelete) + require.NotNil(t, operation, "The DELETE operation on the path /pet could not be found") + + // this should be the raw operationId from the spec + assert.Equal(t, "this-is-a-kebabAndCamel_SNAKE", operation.OperationID) +} diff --git a/internal/test/components/components.gen.go b/internal/test/components/components.gen.go index da630a8053..0bfc1747e6 100644 --- a/internal/test/components/components.gen.go +++ b/internal/test/components/components.gen.go @@ -1,6 +1,6 @@ // Package components provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package components import ( @@ -8,7 +8,7 @@ import ( "errors" "fmt" - "github.com/deepmap/oapi-codegen/pkg/runtime" + "github.com/oapi-codegen/runtime" ) // Defines values for Enum1. @@ -18,6 +18,20 @@ const ( Enum1Two Enum1 = "Two" ) +// Valid indicates whether the value is a known member of the Enum1 enum. +func (e Enum1) Valid() bool { + switch e { + case Enum1One: + return true + case Enum1Three: + return true + case Enum1Two: + return true + default: + return false + } +} + // Defines values for Enum2. const ( Enum2Four Enum2 = "Four" @@ -25,6 +39,20 @@ const ( Enum2Two Enum2 = "Two" ) +// Valid indicates whether the value is a known member of the Enum2 enum. +func (e Enum2) Valid() bool { + switch e { + case Enum2Four: + return true + case Enum2Three: + return true + case Enum2Two: + return true + default: + return false + } +} + // Defines values for Enum3. const ( Enum3Bar Enum3 = "Bar" @@ -32,6 +60,20 @@ const ( Enum3Foo Enum3 = "Foo" ) +// Valid indicates whether the value is a known member of the Enum3 enum. +func (e Enum3) Valid() bool { + switch e { + case Enum3Bar: + return true + case Enum3Enum1One: + return true + case Enum3Foo: + return true + default: + return false + } +} + // Defines values for Enum4. const ( Cat Enum4 = "Cat" @@ -39,6 +81,20 @@ const ( Mouse Enum4 = "Mouse" ) +// Valid indicates whether the value is a known member of the Enum4 enum. +func (e Enum4) Valid() bool { + switch e { + case Cat: + return true + case Dog: + return true + case Mouse: + return true + default: + return false + } +} + // Defines values for Enum5. const ( Enum5N5 Enum5 = 5 @@ -46,6 +102,20 @@ const ( Enum5N7 Enum5 = 7 ) +// Valid indicates whether the value is a known member of the Enum5 enum. +func (e Enum5) Valid() bool { + switch e { + case Enum5N5: + return true + case Enum5N6: + return true + case Enum5N7: + return true + default: + return false + } +} + // Defines values for EnumUnion. const ( EnumUnionFour EnumUnion = "Four" @@ -54,6 +124,22 @@ const ( EnumUnionTwo EnumUnion = "Two" ) +// Valid indicates whether the value is a known member of the EnumUnion enum. +func (e EnumUnion) Valid() bool { + switch e { + case EnumUnionFour: + return true + case EnumUnionOne: + return true + case EnumUnionThree: + return true + case EnumUnionTwo: + return true + default: + return false + } +} + // Defines values for EnumUnion2. const ( EnumUnion2One EnumUnion2 = "One" @@ -62,6 +148,22 @@ const ( EnumUnion2Two EnumUnion2 = "Two" ) +// Valid indicates whether the value is a known member of the EnumUnion2 enum. +func (e EnumUnion2) Valid() bool { + switch e { + case EnumUnion2One: + return true + case EnumUnion2Seven: + return true + case EnumUnion2Three: + return true + case EnumUnion2Two: + return true + default: + return false + } +} + // Defines values for FunnyValues. const ( FunnyValuesAnd FunnyValues = "&" @@ -71,6 +173,24 @@ const ( FunnyValuesPercent FunnyValues = "%" ) +// Valid indicates whether the value is a known member of the FunnyValues enum. +func (e FunnyValues) Valid() bool { + switch e { + case FunnyValuesAnd: + return true + case FunnyValuesAsterisk: + return true + case FunnyValuesEmpty: + return true + case FunnyValuesN5: + return true + case FunnyValuesPercent: + return true + default: + return false + } +} + // Defines values for EnumParam1. const ( EnumParam1Both EnumParam1 = "both" @@ -78,6 +198,20 @@ const ( EnumParam1On EnumParam1 = "on" ) +// Valid indicates whether the value is a known member of the EnumParam1 enum. +func (e EnumParam1) Valid() bool { + switch e { + case EnumParam1Both: + return true + case EnumParam1Off: + return true + case EnumParam1On: + return true + default: + return false + } +} + // Defines values for EnumParam2. const ( EnumParam2Both EnumParam2 = "both" @@ -85,6 +219,20 @@ const ( EnumParam2On EnumParam2 = "on" ) +// Valid indicates whether the value is a known member of the EnumParam2 enum. +func (e EnumParam2) Valid() bool { + switch e { + case EnumParam2Both: + return true + case EnumParam2Off: + return true + case EnumParam2On: + return true + default: + return false + } +} + // Defines values for EnumParam3. const ( Alice EnumParam3 = "alice" @@ -92,6 +240,20 @@ const ( Eve EnumParam3 = "eve" ) +// Valid indicates whether the value is a known member of the EnumParam3 enum. +func (e EnumParam3) Valid() bool { + switch e { + case Alice: + return true + case Bob: + return true + case Eve: + return true + default: + return false + } +} + // AdditionalPropertiesObject1 Has additional properties of type int type AdditionalPropertiesObject1 struct { Id int `json:"id"` @@ -131,6 +293,9 @@ type AdditionalPropertiesObject5 map[string]SchemaObject // AdditionalPropertiesObject6 Array of object with additional properties type AdditionalPropertiesObject6 = []map[string]SchemaObject +// AdditionalPropertiesObject7 Has additional properties with schema for dictionaries +type AdditionalPropertiesObject7 map[string]*SchemaObjectNullable + // AnyOfObject1 simple anyOf case type AnyOfObject1 struct { union json.RawMessage @@ -250,7 +415,7 @@ type OneOfObject3_Union struct { union json.RawMessage } -// OneOfObject4 oneOf plus fixed type - custom marshaling/unmarshalling +// OneOfObject4 oneOf plus fixed type - custom marshaling/unmarshaling type OneOfObject4 struct { FixedProperty *string `json:"fixedProperty,omitempty"` union json.RawMessage @@ -266,6 +431,16 @@ type OneOfObject6 struct { union json.RawMessage } +// OneOfObject61 oneOf with discriminator and partial mapping +type OneOfObject61 struct { + union json.RawMessage +} + +// OneOfObject62 oneOf with snake_case discriminator and partial snake_case mapping +type OneOfObject62 struct { + union json.RawMessage +} + // OneOfObject7 array of oneOf type OneOfObject7 = []OneOfObject7_Item @@ -280,7 +455,7 @@ type OneOfObject8 struct { union json.RawMessage } -// OneOfObject9 oneOf with fixed descriminator +// OneOfObject9 oneOf with fixed discriminator type OneOfObject9 struct { Type string `json:"type"` union json.RawMessage @@ -316,7 +491,7 @@ type OneOfVariant6 struct { // ReferenceToRenameMe When a Schema is renamed, $ref should refer to the new name type ReferenceToRenameMe struct { - // ToNewName This schema should be renamed via x-go-name when generating + // NewName This schema should be renamed via x-go-name when generating NewName NewName `json:"ToNewName"` } @@ -337,6 +512,23 @@ type SchemaObject struct { WriteOnlyRequiredProp *int `json:"writeOnlyRequiredProp,omitempty"` } +// SchemaObjectNullable defines model for SchemaObjectNullable. +type SchemaObjectNullable struct { + FirstName string `json:"firstName"` + + // ReadOnlyRequiredProp This property is required and readOnly, so the go model should have it as a pointer, + // as it will not be included when it is sent from client to server. + ReadOnlyRequiredProp *string `json:"readOnlyRequiredProp,omitempty"` + Role string `json:"role"` + WriteOnlyRequiredProp *int `json:"writeOnlyRequiredProp,omitempty"` +} + +// OneOfVariant51 defines model for one_of_variant51. +type OneOfVariant51 struct { + Discriminator string `json:"discriminator"` + Id int `json:"id"` +} + // EnumParam1 defines model for EnumParam1. type EnumParam1 string @@ -442,7 +634,7 @@ func (a *BodyWithAddPropsJSONBody) UnmarshalJSON(b []byte) error { var fieldVal interface{} err := json.Unmarshal(fieldBuf, &fieldVal) if err != nil { - return fmt.Errorf("error unmarshalling field %s: %w", fieldName, err) + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) } a.AdditionalProperties[fieldName] = fieldVal } @@ -529,7 +721,7 @@ func (a *AdditionalPropertiesObject1) UnmarshalJSON(b []byte) error { var fieldVal int err := json.Unmarshal(fieldBuf, &fieldVal) if err != nil { - return fmt.Errorf("error unmarshalling field %s: %w", fieldName, err) + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) } a.AdditionalProperties[fieldName] = fieldVal } @@ -607,7 +799,7 @@ func (a *AdditionalPropertiesObject3) UnmarshalJSON(b []byte) error { var fieldVal interface{} err := json.Unmarshal(fieldBuf, &fieldVal) if err != nil { - return fmt.Errorf("error unmarshalling field %s: %w", fieldName, err) + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) } a.AdditionalProperties[fieldName] = fieldVal } @@ -681,7 +873,7 @@ func (a *AdditionalPropertiesObject4) UnmarshalJSON(b []byte) error { var fieldVal interface{} err := json.Unmarshal(fieldBuf, &fieldVal) if err != nil { - return fmt.Errorf("error unmarshalling field %s: %w", fieldName, err) + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) } a.AdditionalProperties[fieldName] = fieldVal } @@ -752,7 +944,7 @@ func (a *AdditionalPropertiesObject4_Inner) UnmarshalJSON(b []byte) error { var fieldVal interface{} err := json.Unmarshal(fieldBuf, &fieldVal) if err != nil { - return fmt.Errorf("error unmarshalling field %s: %w", fieldName, err) + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) } a.AdditionalProperties[fieldName] = fieldVal } @@ -817,7 +1009,7 @@ func (t *AnyOfObject1) MergeOneOfVariant4(v OneOfVariant4) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -843,7 +1035,7 @@ func (t *AnyOfObject1) MergeOneOfVariant5(v OneOfVariant5) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -879,7 +1071,7 @@ func (t *OneOfObject1) MergeOneOfVariant1(v OneOfVariant1) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -905,7 +1097,7 @@ func (t *OneOfObject1) MergeOneOfVariant2(v OneOfVariant2) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -931,7 +1123,7 @@ func (t *OneOfObject1) MergeOneOfVariant3(v OneOfVariant3) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -967,7 +1159,7 @@ func (t *OneOfObject10) MergeOneOfObject100(v OneOfObject100) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -993,7 +1185,7 @@ func (t *OneOfObject10) MergeOneOfObject101(v OneOfObject101) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1091,7 +1283,7 @@ func (t *OneOfObject11_AdditionalProperties) MergeOneOfObject110(v OneOfObject11 return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1117,7 +1309,7 @@ func (t *OneOfObject11_AdditionalProperties) MergeOneOfObject111(v OneOfObject11 return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1143,7 +1335,7 @@ func (t *OneOfObject11_AdditionalProperties) MergeOneOfObject112(v OneOfObject11 return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1179,7 +1371,7 @@ func (t *OneOfObject12) MergeOneOfObject120(v OneOfObject120) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1205,7 +1397,7 @@ func (t *OneOfObject12) MergeOneOfObject121(v OneOfObject121) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1231,7 +1423,7 @@ func (t *OneOfObject12) MergeOneOfVariant3(v OneOfVariant3) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1257,7 +1449,7 @@ func (t *OneOfObject12) MergeOneOfVariant4(v OneOfVariant4) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1297,7 +1489,7 @@ func (t *OneOfObject13) MergeOneOfVariant1(v OneOfVariant1) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1327,7 +1519,7 @@ func (t *OneOfObject13) MergeOneOfVariant6(v OneOfVariant6) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1376,7 +1568,7 @@ func (t *OneOfObject2) MergeOneOfObject20(v OneOfObject20) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1402,7 +1594,7 @@ func (t *OneOfObject2) MergeOneOfObject21(v OneOfObject21) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1428,7 +1620,7 @@ func (t *OneOfObject2) MergeOneOfObject22(v OneOfObject22) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1464,7 +1656,7 @@ func (t *OneOfObject3_Union) MergeOneOfVariant1(v OneOfVariant1) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1490,7 +1682,7 @@ func (t *OneOfObject3_Union) MergeOneOfVariant2(v OneOfVariant2) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1516,7 +1708,7 @@ func (t *OneOfObject3_Union) MergeOneOfVariant3(v OneOfVariant3) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1552,7 +1744,7 @@ func (t *OneOfObject4) MergeOneOfVariant1(v OneOfVariant1) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1578,7 +1770,7 @@ func (t *OneOfObject4) MergeOneOfVariant2(v OneOfVariant2) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1604,7 +1796,7 @@ func (t *OneOfObject4) MergeOneOfVariant3(v OneOfVariant3) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1662,6 +1854,7 @@ func (t OneOfObject5) AsOneOfVariant4() (OneOfVariant4, error) { // FromOneOfVariant4 overwrites any union data inside the OneOfObject5 as the provided OneOfVariant4 func (t *OneOfObject5) FromOneOfVariant4(v OneOfVariant4) error { + v.Discriminator = "OneOfVariant4" b, err := json.Marshal(v) t.union = b return err @@ -1669,12 +1862,13 @@ func (t *OneOfObject5) FromOneOfVariant4(v OneOfVariant4) error { // MergeOneOfVariant4 performs a merge with any union data inside the OneOfObject5, using the provided OneOfVariant4 func (t *OneOfObject5) MergeOneOfVariant4(v OneOfVariant4) error { + v.Discriminator = "OneOfVariant4" b, err := json.Marshal(v) if err != nil { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1688,6 +1882,7 @@ func (t OneOfObject5) AsOneOfVariant5() (OneOfVariant5, error) { // FromOneOfVariant5 overwrites any union data inside the OneOfObject5 as the provided OneOfVariant5 func (t *OneOfObject5) FromOneOfVariant5(v OneOfVariant5) error { + v.Discriminator = "OneOfVariant5" b, err := json.Marshal(v) t.union = b return err @@ -1695,12 +1890,13 @@ func (t *OneOfObject5) FromOneOfVariant5(v OneOfVariant5) error { // MergeOneOfVariant5 performs a merge with any union data inside the OneOfObject5, using the provided OneOfVariant5 func (t *OneOfObject5) MergeOneOfVariant5(v OneOfVariant5) error { + v.Discriminator = "OneOfVariant5" b, err := json.Marshal(v) if err != nil { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1713,6 +1909,21 @@ func (t OneOfObject5) Discriminator() (string, error) { return discriminator.Discriminator, err } +func (t OneOfObject5) ValueByDiscriminator() (interface{}, error) { + discriminator, err := t.Discriminator() + if err != nil { + return nil, err + } + switch discriminator { + case "OneOfVariant4": + return t.AsOneOfVariant4() + case "OneOfVariant5": + return t.AsOneOfVariant5() + default: + return nil, errors.New("unknown discriminator value: " + discriminator) + } +} + func (t OneOfObject5) MarshalJSON() ([]byte, error) { b, err := t.union.MarshalJSON() return b, err @@ -1746,7 +1957,7 @@ func (t *OneOfObject6) MergeOneOfVariant4(v OneOfVariant4) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1774,7 +1985,7 @@ func (t *OneOfObject6) MergeOneOfVariant5(v OneOfVariant5) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1812,6 +2023,184 @@ func (t *OneOfObject6) UnmarshalJSON(b []byte) error { return err } +// AsOneOfVariant4 returns the union data inside the OneOfObject61 as a OneOfVariant4 +func (t OneOfObject61) AsOneOfVariant4() (OneOfVariant4, error) { + var body OneOfVariant4 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromOneOfVariant4 overwrites any union data inside the OneOfObject61 as the provided OneOfVariant4 +func (t *OneOfObject61) FromOneOfVariant4(v OneOfVariant4) error { + v.Discriminator = "v4" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeOneOfVariant4 performs a merge with any union data inside the OneOfObject61, using the provided OneOfVariant4 +func (t *OneOfObject61) MergeOneOfVariant4(v OneOfVariant4) error { + v.Discriminator = "v4" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsOneOfVariant5 returns the union data inside the OneOfObject61 as a OneOfVariant5 +func (t OneOfObject61) AsOneOfVariant5() (OneOfVariant5, error) { + var body OneOfVariant5 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromOneOfVariant5 overwrites any union data inside the OneOfObject61 as the provided OneOfVariant5 +func (t *OneOfObject61) FromOneOfVariant5(v OneOfVariant5) error { + v.Discriminator = "OneOfVariant5" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeOneOfVariant5 performs a merge with any union data inside the OneOfObject61, using the provided OneOfVariant5 +func (t *OneOfObject61) MergeOneOfVariant5(v OneOfVariant5) error { + v.Discriminator = "OneOfVariant5" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t OneOfObject61) Discriminator() (string, error) { + var discriminator struct { + Discriminator string `json:"discriminator"` + } + err := json.Unmarshal(t.union, &discriminator) + return discriminator.Discriminator, err +} + +func (t OneOfObject61) ValueByDiscriminator() (interface{}, error) { + discriminator, err := t.Discriminator() + if err != nil { + return nil, err + } + switch discriminator { + case "OneOfVariant5": + return t.AsOneOfVariant5() + case "v4": + return t.AsOneOfVariant4() + default: + return nil, errors.New("unknown discriminator value: " + discriminator) + } +} + +func (t OneOfObject61) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *OneOfObject61) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsOneOfVariant4 returns the union data inside the OneOfObject62 as a OneOfVariant4 +func (t OneOfObject62) AsOneOfVariant4() (OneOfVariant4, error) { + var body OneOfVariant4 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromOneOfVariant4 overwrites any union data inside the OneOfObject62 as the provided OneOfVariant4 +func (t *OneOfObject62) FromOneOfVariant4(v OneOfVariant4) error { + v.Discriminator = "variant_four" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeOneOfVariant4 performs a merge with any union data inside the OneOfObject62, using the provided OneOfVariant4 +func (t *OneOfObject62) MergeOneOfVariant4(v OneOfVariant4) error { + v.Discriminator = "variant_four" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsOneOfVariant51 returns the union data inside the OneOfObject62 as a OneOfVariant51 +func (t OneOfObject62) AsOneOfVariant51() (OneOfVariant51, error) { + var body OneOfVariant51 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromOneOfVariant51 overwrites any union data inside the OneOfObject62 as the provided OneOfVariant51 +func (t *OneOfObject62) FromOneOfVariant51(v OneOfVariant51) error { + v.Discriminator = "one_of_variant51" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeOneOfVariant51 performs a merge with any union data inside the OneOfObject62, using the provided OneOfVariant51 +func (t *OneOfObject62) MergeOneOfVariant51(v OneOfVariant51) error { + v.Discriminator = "one_of_variant51" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t OneOfObject62) Discriminator() (string, error) { + var discriminator struct { + Discriminator string `json:"discriminator"` + } + err := json.Unmarshal(t.union, &discriminator) + return discriminator.Discriminator, err +} + +func (t OneOfObject62) ValueByDiscriminator() (interface{}, error) { + discriminator, err := t.Discriminator() + if err != nil { + return nil, err + } + switch discriminator { + case "one_of_variant51": + return t.AsOneOfVariant51() + case "variant_four": + return t.AsOneOfVariant4() + default: + return nil, errors.New("unknown discriminator value: " + discriminator) + } +} + +func (t OneOfObject62) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *OneOfObject62) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + // AsOneOfVariant1 returns the union data inside the OneOfObject7_Item as a OneOfVariant1 func (t OneOfObject7_Item) AsOneOfVariant1() (OneOfVariant1, error) { var body OneOfVariant1 @@ -1833,7 +2222,7 @@ func (t *OneOfObject7_Item) MergeOneOfVariant1(v OneOfVariant1) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1859,7 +2248,7 @@ func (t *OneOfObject7_Item) MergeOneOfVariant2(v OneOfVariant2) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1895,7 +2284,7 @@ func (t *OneOfObject8) MergeOneOfVariant1(v OneOfVariant1) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1921,7 +2310,7 @@ func (t *OneOfObject8) MergeOneOfVariant2(v OneOfVariant2) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -1995,7 +2384,7 @@ func (t *OneOfObject9) MergeOneOfVariant1(v OneOfVariant1) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -2025,7 +2414,7 @@ func (t *OneOfObject9) MergeOneOfVariant6(v OneOfVariant6) error { return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } diff --git a/internal/test/components/components.yaml b/internal/test/components/components.yaml index d60c810795..f711510e37 100644 --- a/internal/test/components/components.yaml +++ b/internal/test/components/components.yaml @@ -137,7 +137,7 @@ components: type: string readOnlyRequiredProp: description: | - This property is required and readOnly, so the go model should have it as a pointer, + This property is required and readOnly, so the go model should have it as a pointer, as it will not be included when it is sent from client to server. type: string readOnly: true @@ -149,6 +149,27 @@ components: - firstName - readOnlyRequiredProp - writeOnlyRequiredProp + SchemaObjectNullable: + properties: + role: + type: string + firstName: + type: string + readOnlyRequiredProp: + description: | + This property is required and readOnly, so the go model should have it as a pointer, + as it will not be included when it is sent from client to server. + type: string + readOnly: true + writeOnlyRequiredProp: + type: integer + writeOnly: true + required: + - role + - firstName + - readOnlyRequiredProp + - writeOnlyRequiredProp + nullable: true AdditionalPropertiesObject1: description: Has additional properties of type int type: object @@ -207,6 +228,11 @@ components: type: object additionalProperties: $ref: '#/components/schemas/SchemaObject' + AdditionalPropertiesObject7: + description: Has additional properties with schema for dictionaries + type: object + additionalProperties: + $ref: '#/components/schemas/SchemaObjectNullable' OneOfObject1: description: oneOf with references and no discriminator oneOf: @@ -234,7 +260,7 @@ components: - $ref: '#/components/schemas/OneOfVariant2' - $ref: '#/components/schemas/OneOfVariant3' OneOfObject4: - description: oneOf plus fixed type - custom marshaling/unmarshalling + description: oneOf plus fixed type - custom marshaling/unmarshaling type: object properties: fixedProperty: @@ -260,6 +286,24 @@ components: mapping: v4: '#/components/schemas/OneOfVariant4' v5: '#/components/schemas/OneOfVariant5' + OneOfObject61: + description: oneOf with discriminator and partial mapping + oneOf: + - $ref: '#/components/schemas/OneOfVariant4' + - $ref: '#/components/schemas/OneOfVariant5' + discriminator: + propertyName: discriminator + mapping: + v4: '#/components/schemas/OneOfVariant4' + OneOfObject62: + description: oneOf with snake_case discriminator and partial snake_case mapping + oneOf: + - $ref: '#/components/schemas/OneOfVariant4' + - $ref: '#/components/schemas/one_of_variant51' + discriminator: + propertyName: discriminator + mapping: + variant_four: '#/components/schemas/OneOfVariant4' OneOfObject7: description: array of oneOf type: array @@ -277,7 +321,7 @@ components: - $ref: '#/components/schemas/OneOfVariant1' - $ref: '#/components/schemas/OneOfVariant2' OneOfObject9: - description: oneOf with fixed descriminator + description: oneOf with fixed discriminator type: object properties: type: @@ -380,6 +424,16 @@ components: required: - discriminator - id + one_of_variant51: + type: object + properties: + discriminator: + type: string + id: + type: integer + required: + - discriminator + - id OneOfVariant6: type: object properties: diff --git a/internal/test/components/components_test.go b/internal/test/components/components_test.go index e25ede26bc..268612a3d6 100644 --- a/internal/test/components/components_test.go +++ b/internal/test/components/components_test.go @@ -9,19 +9,12 @@ import ( ) func assertJsonEqual(t *testing.T, j1 []byte, j2 []byte) { - var v1, v2 interface{} - - err := json.Unmarshal(j1, &v1) - assert.NoError(t, err) - - err = json.Unmarshal(j2, &v2) - assert.NoError(t, err) - - assert.EqualValues(t, v1, v2) + t.Helper() + assert.JSONEq(t, string(j1), string(j2)) } func TestRawJSON(t *testing.T) { - // Check raw json unmarshalling + // Check raw json unmarshaling const buf = `{"name":"bob","value1":{"present":true}}` var dst ObjectWithJsonField err := json.Unmarshal([]byte(buf), &dst) @@ -59,7 +52,7 @@ func TestAdditionalProperties(t *testing.T) { assert.True(t, found) assert.EqualValues(t, 7, foo) - // test that additionalProperties that reference a schema work when unmarshalling + // test that additionalProperties that reference a schema work when unmarshaling bossSchema := SchemaObject{ FirstName: "bob", Role: "warehouse manager", @@ -70,6 +63,20 @@ func TestAdditionalProperties(t *testing.T) { err = json.Unmarshal([]byte(buf2), &obj5) assert.NoError(t, err) assert.Equal(t, bossSchema, obj5["boss"]) + + bossSchemaNullable := &SchemaObjectNullable{ + FirstName: "bob", + Role: "warehouse manager", + } + + buf3 := `{"boss": { "firstName": "bob", "role": "warehouse manager" }, "employee": null}` + var obj7 AdditionalPropertiesObject7 + err = json.Unmarshal([]byte(buf3), &obj7) + assert.NoError(t, err) + employee, ok := obj7["employee"] + assert.True(t, ok) + assert.Equal(t, bossSchemaNullable, obj7["boss"]) + assert.Nil(t, employee) } func TestOneOf(t *testing.T) { @@ -152,6 +159,117 @@ func TestOneOfWithDiscriminator(t *testing.T) { assertJsonEqual(t, []byte(variant5), marshaled) } +func TestOneOfWithDiscriminator_PartialMapping(t *testing.T) { + const variant4 = `{"discriminator": "v4", "name": "123"}` + const variant5 = `{"discriminator": "OneOfVariant5", "id": 321}` + var dst OneOfObject61 + + err := json.Unmarshal([]byte(variant4), &dst) + assert.NoError(t, err) + discriminator, err := dst.Discriminator() + require.NoError(t, err) + assert.Equal(t, "v4", discriminator) + v4, err := dst.ValueByDiscriminator() + require.NoError(t, err) + assert.Equal(t, OneOfVariant4{Discriminator: "v4", Name: "123"}, v4) + + err = json.Unmarshal([]byte(variant5), &dst) + require.NoError(t, err) + discriminator, err = dst.Discriminator() + require.NoError(t, err) + assert.Equal(t, "OneOfVariant5", discriminator) + v5, err := dst.ValueByDiscriminator() + require.NoError(t, err) + assert.Equal(t, OneOfVariant5{Discriminator: "OneOfVariant5", Id: 321}, v5) + + // discriminator value will be filled by the generated code + err = dst.FromOneOfVariant4(OneOfVariant4{Name: "123"}) + require.NoError(t, err) + marshaled, err := json.Marshal(dst) + require.NoError(t, err) + assertJsonEqual(t, []byte(variant4), marshaled) + + err = dst.FromOneOfVariant5(OneOfVariant5{Id: 321}) + require.NoError(t, err) + marshaled, err = json.Marshal(dst) + require.NoError(t, err) + assertJsonEqual(t, []byte(variant5), marshaled) +} + +func TestOneOfWithDiscriminator_SchemaNameUsed(t *testing.T) { + const variant4 = `{"discriminator": "variant_four", "name": "789"}` + const variant51 = `{"discriminator": "one_of_variant51", "id": 987}` + var dst OneOfObject62 + + err := json.Unmarshal([]byte(variant4), &dst) + assert.NoError(t, err) + discriminator, err := dst.Discriminator() + require.NoError(t, err) + assert.Equal(t, "variant_four", discriminator) + v4, err := dst.ValueByDiscriminator() + require.NoError(t, err) + assert.Equal(t, OneOfVariant4{Discriminator: "variant_four", Name: "789"}, v4) + + err = json.Unmarshal([]byte(variant51), &dst) + require.NoError(t, err) + discriminator, err = dst.Discriminator() + require.NoError(t, err) + assert.Equal(t, "one_of_variant51", discriminator) + v5, err := dst.ValueByDiscriminator() + require.NoError(t, err) + assert.Equal(t, OneOfVariant51{Discriminator: "one_of_variant51", Id: 987}, v5) + + // discriminator value will be filled by the generated code + err = dst.FromOneOfVariant4(OneOfVariant4{Name: "789"}) + require.NoError(t, err) + marshaled, err := json.Marshal(dst) + require.NoError(t, err) + assertJsonEqual(t, []byte(variant4), marshaled) + + err = dst.FromOneOfVariant51(OneOfVariant51{Id: 987}) + require.NoError(t, err) + marshaled, err = json.Marshal(dst) + require.NoError(t, err) + assertJsonEqual(t, []byte(variant51), marshaled) +} + +func TestOneOfWithDiscriminator_FullImplicitMapping(t *testing.T) { + const variant4 = `{"discriminator": "OneOfVariant4", "name": "456"}` + const variant5 = `{"discriminator": "OneOfVariant5", "id": 654}` + var dst OneOfObject5 + + err := json.Unmarshal([]byte(variant4), &dst) + assert.NoError(t, err) + discriminator, err := dst.Discriminator() + require.NoError(t, err) + assert.Equal(t, "OneOfVariant4", discriminator) + v4, err := dst.ValueByDiscriminator() + require.NoError(t, err) + assert.Equal(t, OneOfVariant4{Discriminator: "OneOfVariant4", Name: "456"}, v4) + + err = json.Unmarshal([]byte(variant5), &dst) + require.NoError(t, err) + discriminator, err = dst.Discriminator() + require.NoError(t, err) + assert.Equal(t, "OneOfVariant5", discriminator) + v5, err := dst.ValueByDiscriminator() + require.NoError(t, err) + assert.Equal(t, OneOfVariant5{Discriminator: "OneOfVariant5", Id: 654}, v5) + + // discriminator value will be filled by the generated code + err = dst.FromOneOfVariant4(OneOfVariant4{Name: "456"}) + require.NoError(t, err) + marshaled, err := json.Marshal(dst) + require.NoError(t, err) + assertJsonEqual(t, []byte(variant4), marshaled) + + err = dst.FromOneOfVariant5(OneOfVariant5{Id: 654}) + require.NoError(t, err) + marshaled, err = json.Marshal(dst) + require.NoError(t, err) + assertJsonEqual(t, []byte(variant5), marshaled) +} + func TestOneOfWithFixedProperties(t *testing.T) { const variant1 = "{\"type\": \"v1\", \"name\": \"123\"}" const variant6 = "{\"type\": \"v6\", \"values\": [1, 2, 3]}" diff --git a/internal/test/components/config.yaml b/internal/test/components/config.yaml index 1250e0f66a..185203a501 100644 --- a/internal/test/components/config.yaml +++ b/internal/test/components/config.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../configuration-schema.json package: components generate: models: true diff --git a/internal/test/components/doc.go b/internal/test/components/doc.go index b70ba3d7ab..64f995a9c0 100644 --- a/internal/test/components/doc.go +++ b/internal/test/components/doc.go @@ -1,3 +1,3 @@ package components -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml components.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml components.yaml diff --git a/internal/test/cookies/config.yaml b/internal/test/cookies/config.yaml new file mode 100644 index 0000000000..2d1cbd4300 --- /dev/null +++ b/internal/test/cookies/config.yaml @@ -0,0 +1,6 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: cookies +generate: + chi-server: true + models: true +output: cookies.gen.go diff --git a/internal/test/cookies/cookies.gen.go b/internal/test/cookies/cookies.gen.go new file mode 100644 index 0000000000..4e9b6139ec --- /dev/null +++ b/internal/test/cookies/cookies.gen.go @@ -0,0 +1,215 @@ +// Package cookies provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package cookies + +import ( + "fmt" + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/oapi-codegen/runtime" +) + +// CookieParamsParams defines parameters for CookieParams. +type CookieParamsParams struct { + // AuthId Cookie parameter + AuthId *string `form:"authId,omitempty" json:"authId,omitempty"` + + // ServerId Another cookie parameter + ServerId *string `form:"serverId,omitempty" json:"serverId,omitempty"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /cookies) + CookieParams(w http.ResponseWriter, r *http.Request, params CookieParamsParams) +} + +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct{} + +// (GET /cookies) +func (_ Unimplemented) CookieParams(w http.ResponseWriter, r *http.Request, params CookieParamsParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// CookieParams operation middleware +func (siw *ServerInterfaceWrapper) CookieParams(w http.ResponseWriter, r *http.Request) { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params CookieParamsParams + + { + var cookie *http.Cookie + + if cookie, err = r.Cookie("authId"); err == nil { + var value string + err = runtime.BindStyledParameterWithOptions("simple", "authId", cookie.Value, &value, runtime.BindStyledParameterOptions{Explode: true, Required: false, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "authId", Err: err}) + return + } + params.AuthId = &value + + } + } + + { + var cookie *http.Cookie + + if cookie, err = r.Cookie("serverId"); err == nil { + var value string + err = runtime.BindStyledParameterWithOptions("simple", "serverId", cookie.Value, &value, runtime.BindStyledParameterOptions{Explode: true, Required: false, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "serverId", Err: err}) + return + } + params.ServerId = &value + + } + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CookieParams(w, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{}) +} + +type ChiServerOptions struct { + BaseURL string + BaseRouter chi.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = chi.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/cookies", wrapper.CookieParams) + }) + + return r +} diff --git a/internal/test/cookies/generate.go b/internal/test/cookies/generate.go new file mode 100644 index 0000000000..46a9a52676 --- /dev/null +++ b/internal/test/cookies/generate.go @@ -0,0 +1,3 @@ +package cookies + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/cookies/spec.yaml b/internal/test/cookies/spec.yaml new file mode 100644 index 0000000000..f30596224f --- /dev/null +++ b/internal/test/cookies/spec.yaml @@ -0,0 +1,24 @@ +openapi: "3.0.1" +info: + version: 1.0.0 + title: Cookie parameters +paths: + /cookies: + get: + operationId: cookieParams + parameters: + - name: authId + description: Cookie parameter + in: cookie + required: false + schema: + type: string + - name: serverId + description: Another cookie parameter + in: cookie + required: false + schema: + type: string + responses: + 204: + description: no content diff --git a/internal/test/extensions/x-order/config.yaml b/internal/test/extensions/x-order/config.yaml new file mode 100644 index 0000000000..357489f37d --- /dev/null +++ b/internal/test/extensions/x-order/config.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: xorder +generate: + models: true +output: issue.gen.go +output-options: + skip-prune: true diff --git a/internal/test/extensions/x-order/generate.go b/internal/test/extensions/x-order/generate.go new file mode 100644 index 0000000000..8970f968ac --- /dev/null +++ b/internal/test/extensions/x-order/generate.go @@ -0,0 +1,3 @@ +package xorder + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/extensions/x-order/issue.gen.go b/internal/test/extensions/x-order/issue.gen.go new file mode 100644 index 0000000000..c665ee8ad5 --- /dev/null +++ b/internal/test/extensions/x-order/issue.gen.go @@ -0,0 +1,27 @@ +// Package xorder provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package xorder + +import ( + openapi_types "github.com/oapi-codegen/runtime/types" +) + +// DateInterval defines model for DateInterval. +type DateInterval struct { + Start *openapi_types.Date `json:"start,omitempty"` + End *openapi_types.Date `json:"end,omitempty"` +} + +// Port defines model for Port. +type Port = int + +// PortInterval defines model for PortInterval. +type PortInterval struct { + Start Port `json:"start"` + End Port `json:"end"` + VeryEnd *LowPriorityPort `json:"very_end,omitempty"` +} + +// LowPriorityPort defines model for LowPriorityPort. +type LowPriorityPort = int diff --git a/internal/test/extensions/x-order/spec.yaml b/internal/test/extensions/x-order/spec.yaml new file mode 100644 index 0000000000..edc52625da --- /dev/null +++ b/internal/test/extensions/x-order/spec.yaml @@ -0,0 +1,34 @@ +components: + schemas: + DateInterval: + type: object + required: + - name + properties: + end: + type: string + format: date + x-order: 2 + start: + type: string + format: date + x-order: 1 + Port: + type: integer + LowPriorityPort: + type: integer + x-order: 50 + PortInterval: + type: object + required: + - start + - end + properties: + end: + $ref: '#/components/schemas/Port' + x-order: 2 + very_end: + $ref: '#/components/schemas/LowPriorityPort' + start: + $ref: '#/components/schemas/Port' + x-order: 1 diff --git a/internal/test/externalref/doc.go b/internal/test/externalref/doc.go index ea1d5f5ff5..c5aeaa5221 100644 --- a/internal/test/externalref/doc.go +++ b/internal/test/externalref/doc.go @@ -1,3 +1,3 @@ package externalref -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=externalref.cfg.yaml spec.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=externalref.cfg.yaml spec.yaml diff --git a/internal/test/externalref/externalref.cfg.yaml b/internal/test/externalref/externalref.cfg.yaml index cc772e9e99..dd7a93d019 100644 --- a/internal/test/externalref/externalref.cfg.yaml +++ b/internal/test/externalref/externalref.cfg.yaml @@ -1,10 +1,12 @@ +# yaml-language-server: $schema=../../../configuration-schema.json package: externalref generate: models: true embedded-spec: true import-mapping: - ./packageA/spec.yaml: github.com/deepmap/oapi-codegen/internal/test/externalref/packageA - ./packageB/spec.yaml: github.com/deepmap/oapi-codegen/internal/test/externalref/packageB + ./packageA/spec.yaml: github.com/oapi-codegen/oapi-codegen/v2/internal/test/externalref/packageA + ./packageB/spec.yaml: package_b github.com/oapi-codegen/oapi-codegen/v2/internal/test/externalref/packageB + https://petstore3.swagger.io/api/v3/openapi.json: github.com/oapi-codegen/oapi-codegen/v2/internal/test/externalref/petstore output: externalref.gen.go output-options: skip-prune: true diff --git a/internal/test/externalref/externalref.gen.go b/internal/test/externalref/externalref.gen.go index b9b62d1b25..158a922d02 100644 --- a/internal/test/externalref/externalref.gen.go +++ b/internal/test/externalref/externalref.gen.go @@ -1,6 +1,6 @@ // Package externalref provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package externalref import ( @@ -12,25 +12,31 @@ import ( "path" "strings" - externalRef0 "github.com/deepmap/oapi-codegen/internal/test/externalref/packageA" - externalRef1 "github.com/deepmap/oapi-codegen/internal/test/externalref/packageB" "github.com/getkin/kin-openapi/openapi3" + externalRef0 "github.com/oapi-codegen/oapi-codegen/v2/internal/test/externalref/packageA" + package_b "github.com/oapi-codegen/oapi-codegen/v2/internal/test/externalref/packageB" + externalRef1 "github.com/oapi-codegen/oapi-codegen/v2/internal/test/externalref/petstore" ) // Container defines model for Container. type Container struct { ObjectA *externalRef0.ObjectA `json:"object_a,omitempty"` - ObjectB *externalRef1.ObjectB `json:"object_b,omitempty"` + ObjectB *package_b.ObjectB `json:"object_b,omitempty"` ObjectC *map[string]interface{} `json:"object_c,omitempty"` + Pet *externalRef1.Pet `json:"pet,omitempty"` } // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/5yPQU7EMAxF72JYRtOR2GXHcAC4AfJkPNSotaPEIKEqd0dJKa1gQcUqtvzf/z8TBB2j", - "Coll8BPk0NOIbXxQMWShVJeYNFIypnbS8ysFe8Y63ya6goebbjXqvly6x6a7h+IW5LwPOW2Q8BfyrSul", - "OFgyf5UWHKm+9hEJPGRLLC//qrbGnHbHlB8fwsuFjVVweNrglt7ILegsn1GWqzZXtqHewME7pcwqdane", - "kQQjg4e7w/FwBAcRra+NSvkMAAD//3vXjDblAQAA", + "H4sIAAAAAAAC/6RUTW/bMAz9KwG3o9Bk6LCDb213Xw7bqSgMRmYcbbakSUzWoNB/Hyg1jROnSLJdDJof", + "D+890n4B7XrvLFmOUL1A1CvqMYcPzjIaS0FefHCeAhvKJbf4SZprlPhjoCVU8GG6B5q+okw96l/Y0l0d", + "Pen6W566g6R2AIsLAe6HAPcDAH0O4K0vKfDE59rRm3pzWztPVsI5MaSUFBzlH5CpdWE7dsY08qRn7H1H", + "UH1SsHShR4YKjOUvn0EBbz2VV2opCDGLPR2MwVfXxn1r5GBsC0LkNVNkgYLnvpPJggB6x+sE53lRf0hX", + "D4Rc4cub/qRGimf/KLlxbWtoLFqBXzl2P0JXDGbqc3DYduzEbmZoGoaA233nn4DeUwMVhzVJW2TkdcZu", + "KOpgPBtnBYt4UmoTYye8oklkF4Qq2XUP1SPgBk2Hi05ynmxTGEXXNfB0QhBje6jlCuu/Y4G4RFJSEOj3", + "2gRJPRZrhnY+nbsnn+9/dErC4Z3Lv2L11x43Y+kafvrYNEa2hN18QEbUH6PJHZ38G42EvMPvv39aaU/h", + "qHQphZQxjF26XDScPxxQsKEQy61mnmVNUMHtzexmJitHXglwSn8DAAD//xIv2MTwBQAA", } // GetSwagger returns the content of the embedded swagger specification file @@ -38,16 +44,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -65,20 +71,24 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } - pathPrefix := path.Dir(pathToFile) - - for rawPath, rawFunc := range externalRef0.PathToRawSpec(path.Join(pathPrefix, "./packageA/spec.yaml")) { + for rawPath, rawFunc := range externalRef0.PathToRawSpec(path.Join(path.Dir(pathToFile), "./packageA/spec.yaml")) { + if _, ok := res[rawPath]; ok { + // it is not possible to compare functions in golang, so always overwrite the old value + } + res[rawPath] = rawFunc + } + for rawPath, rawFunc := range package_b.PathToRawSpec(path.Join(path.Dir(pathToFile), "./packageB/spec.yaml")) { if _, ok := res[rawPath]; ok { // it is not possible to compare functions in golang, so always overwrite the old value } res[rawPath] = rawFunc } - for rawPath, rawFunc := range externalRef1.PathToRawSpec(path.Join(pathPrefix, "./packageB/spec.yaml")) { + for rawPath, rawFunc := range externalRef1.PathToRawSpec(path.Join(path.Dir(pathToFile), "https://petstore3.swagger.io/api/v3/openapi.json")) { if _, ok := res[rawPath]; ok { // it is not possible to compare functions in golang, so always overwrite the old value } @@ -93,12 +103,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/internal/test/externalref/imports_test.go b/internal/test/externalref/imports_test.go index 607def3ffa..34fd0ec11e 100644 --- a/internal/test/externalref/imports_test.go +++ b/internal/test/externalref/imports_test.go @@ -3,8 +3,9 @@ package externalref import ( "testing" - packageA "github.com/deepmap/oapi-codegen/internal/test/externalref/packageA" - packageB "github.com/deepmap/oapi-codegen/internal/test/externalref/packageB" + packageA "github.com/oapi-codegen/oapi-codegen/v2/internal/test/externalref/packageA" + packageB "github.com/oapi-codegen/oapi-codegen/v2/internal/test/externalref/packageB" + petstore "github.com/oapi-codegen/oapi-codegen/v2/internal/test/externalref/petstore" "github.com/stretchr/testify/require" ) @@ -23,6 +24,9 @@ func TestGetSwagger(t *testing.T) { _, err = packageA.GetSwagger() require.Nil(t, err) + _, err = petstore.GetSwagger() + require.Nil(t, err) + _, err = GetSwagger() require.Nil(t, err) } diff --git a/internal/test/externalref/packageA/config.yaml b/internal/test/externalref/packageA/config.yaml index 03240de6cd..6ee1a13699 100644 --- a/internal/test/externalref/packageA/config.yaml +++ b/internal/test/externalref/packageA/config.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: packagea generate: models: true @@ -5,5 +6,5 @@ generate: output-options: skip-prune: true import-mapping: - ../packageB/spec.yaml: github.com/deepmap/oapi-codegen/internal/test/externalref/packageB + ../packageB/spec.yaml: github.com/oapi-codegen/oapi-codegen/v2/internal/test/externalref/packageB output: externalref.gen.go diff --git a/internal/test/externalref/packageA/doc.go b/internal/test/externalref/packageA/doc.go index 8a7bb68eb1..f05471ffbb 100644 --- a/internal/test/externalref/packageA/doc.go +++ b/internal/test/externalref/packageA/doc.go @@ -1,3 +1,3 @@ package packagea -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml spec.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/externalref/packageA/externalref.gen.go b/internal/test/externalref/packageA/externalref.gen.go index 1a4d648dd2..582588326c 100644 --- a/internal/test/externalref/packageA/externalref.gen.go +++ b/internal/test/externalref/packageA/externalref.gen.go @@ -1,6 +1,6 @@ // Package packagea provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package packagea import ( @@ -12,8 +12,8 @@ import ( "path" "strings" - externalRef0 "github.com/deepmap/oapi-codegen/internal/test/externalref/packageB" "github.com/getkin/kin-openapi/openapi3" + externalRef0 "github.com/oapi-codegen/oapi-codegen/v2/internal/test/externalref/packageB" ) // ObjectA defines model for ObjectA. @@ -25,9 +25,9 @@ type ObjectA struct { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/4yNQa7CMAxE7zL/LyN1nx1cgCOgtHJpUGtbiVmgyndHSUFsWXmsp3mzY5JNhYmtIu6o", - "00Jb6vEy3mmyU4taRKlYpg44bdSuPZUQUa1kvsEDpDeuY4P/hWZE/A1f//CWD4f5DHcP+Dy/znhvZZ4F", - "kR/rGiBKnDQjAgGabKkH8VcAAAD//0SMp77dAAAA", + "H4sIAAAAAAAC/4yOMcrDMAxG7/L9/xjI7q25QI8QHKMkbhNZ2OpQgu5e7BS6dOikBw896UBIuyQm1gJ3", + "oISVdt/wOt0o6KWi5CSUNVIT7HeqU59CcCiaIy+wDqltjFOV/5lmOPz1n37/jvfiw90vNIxFKIznnQFm", + "1uG7+vUFa43Ic4Ljx7Z1SELsJcIBNa5rOY29AgAA//84dUj5+QAAAA==", } // GetSwagger returns the content of the embedded swagger specification file @@ -35,16 +35,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -62,14 +62,12 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } - pathPrefix := path.Dir(pathToFile) - - for rawPath, rawFunc := range externalRef0.PathToRawSpec(path.Join(pathPrefix, "../packageB/spec.yaml")) { + for rawPath, rawFunc := range externalRef0.PathToRawSpec(path.Join(path.Dir(pathToFile), "../packageB/spec.yaml")) { if _, ok := res[rawPath]; ok { // it is not possible to compare functions in golang, so always overwrite the old value } @@ -84,12 +82,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/internal/test/externalref/packageB/config.yaml b/internal/test/externalref/packageB/config.yaml index 748a18858b..3dc9b7ce7b 100644 --- a/internal/test/externalref/packageB/config.yaml +++ b/internal/test/externalref/packageB/config.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: packageb generate: models: true diff --git a/internal/test/externalref/packageB/doc.go b/internal/test/externalref/packageB/doc.go index 73641dad9f..0dc18f8255 100644 --- a/internal/test/externalref/packageB/doc.go +++ b/internal/test/externalref/packageB/doc.go @@ -1,3 +1,3 @@ package packageb -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml spec.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/externalref/packageB/externalref.gen.go b/internal/test/externalref/packageB/externalref.gen.go index 3e4fe051e6..c3f879cfda 100644 --- a/internal/test/externalref/packageB/externalref.gen.go +++ b/internal/test/externalref/packageB/externalref.gen.go @@ -1,6 +1,6 @@ // Package packageb provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package packageb import ( @@ -32,16 +32,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -59,7 +59,7 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } @@ -73,12 +73,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/internal/test/externalref/petstore/config.yaml b/internal/test/externalref/petstore/config.yaml new file mode 100644 index 0000000000..6ee1a13699 --- /dev/null +++ b/internal/test/externalref/petstore/config.yaml @@ -0,0 +1,10 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: packagea +generate: + models: true + embedded-spec: true +output-options: + skip-prune: true +import-mapping: + ../packageB/spec.yaml: github.com/oapi-codegen/oapi-codegen/v2/internal/test/externalref/packageB +output: externalref.gen.go diff --git a/internal/test/externalref/petstore/doc.go b/internal/test/externalref/petstore/doc.go new file mode 100644 index 0000000000..f05471ffbb --- /dev/null +++ b/internal/test/externalref/petstore/doc.go @@ -0,0 +1,3 @@ +package packagea + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/externalref/petstore/externalref.gen.go b/internal/test/externalref/petstore/externalref.gen.go new file mode 100644 index 0000000000..e8102f0d28 --- /dev/null +++ b/internal/test/externalref/petstore/externalref.gen.go @@ -0,0 +1,382 @@ +// Package packagea provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package packagea + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "net/url" + "path" + "strings" + "time" + + "github.com/getkin/kin-openapi/openapi3" + externalRef0 "github.com/oapi-codegen/oapi-codegen/v2/internal/test/externalref/packageB" +) + +const ( + Api_keyScopes = "api_key.Scopes" + Petstore_authScopes = "petstore_auth.Scopes" +) + +// Defines values for OrderStatus. +const ( + Approved OrderStatus = "approved" + Delivered OrderStatus = "delivered" + Placed OrderStatus = "placed" +) + +// Valid indicates whether the value is a known member of the OrderStatus enum. +func (e OrderStatus) Valid() bool { + switch e { + case Approved: + return true + case Delivered: + return true + case Placed: + return true + default: + return false + } +} + +// Defines values for PetStatus. +const ( + PetStatusAvailable PetStatus = "available" + PetStatusPending PetStatus = "pending" + PetStatusSold PetStatus = "sold" +) + +// Valid indicates whether the value is a known member of the PetStatus enum. +func (e PetStatus) Valid() bool { + switch e { + case PetStatusAvailable: + return true + case PetStatusPending: + return true + case PetStatusSold: + return true + default: + return false + } +} + +// Defines values for FindPetsByStatusParamsStatus. +const ( + FindPetsByStatusParamsStatusAvailable FindPetsByStatusParamsStatus = "available" + FindPetsByStatusParamsStatusPending FindPetsByStatusParamsStatus = "pending" + FindPetsByStatusParamsStatusSold FindPetsByStatusParamsStatus = "sold" +) + +// Valid indicates whether the value is a known member of the FindPetsByStatusParamsStatus enum. +func (e FindPetsByStatusParamsStatus) Valid() bool { + switch e { + case FindPetsByStatusParamsStatusAvailable: + return true + case FindPetsByStatusParamsStatusPending: + return true + case FindPetsByStatusParamsStatusSold: + return true + default: + return false + } +} + +// Address defines model for Address. +type Address struct { + City *string `json:"city,omitempty"` + State *string `json:"state,omitempty"` + Street *string `json:"street,omitempty"` + Zip *string `json:"zip,omitempty"` +} + +// ApiResponse defines model for ApiResponse. +type ApiResponse struct { + Code *int32 `json:"code,omitempty"` + Message *string `json:"message,omitempty"` + Type *string `json:"type,omitempty"` +} + +// Category defines model for Category. +type Category struct { + Id *int64 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` +} + +// Customer defines model for Customer. +type Customer struct { + Address *[]Address `json:"address,omitempty"` + Id *int64 `json:"id,omitempty"` + Username *string `json:"username,omitempty"` +} + +// Order defines model for Order. +type Order struct { + Complete *bool `json:"complete,omitempty"` + Id *int64 `json:"id,omitempty"` + PetId *int64 `json:"petId,omitempty"` + Quantity *int32 `json:"quantity,omitempty"` + ShipDate *time.Time `json:"shipDate,omitempty"` + + // Status Order Status + Status *OrderStatus `json:"status,omitempty"` +} + +// OrderStatus Order Status +type OrderStatus string + +// Pet defines model for Pet. +type Pet struct { + Category *Category `json:"category,omitempty"` + Id *int64 `json:"id,omitempty"` + Name string `json:"name"` + PhotoUrls []string `json:"photoUrls"` + + // Status pet status in the store + Status *PetStatus `json:"status,omitempty"` + Tags *[]Tag `json:"tags,omitempty"` +} + +// PetStatus pet status in the store +type PetStatus string + +// Tag defines model for Tag. +type Tag struct { + Id *int64 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` +} + +// User defines model for User. +type User struct { + Email *string `json:"email,omitempty"` + FirstName *string `json:"firstName,omitempty"` + Id *int64 `json:"id,omitempty"` + LastName *string `json:"lastName,omitempty"` + Password *string `json:"password,omitempty"` + Phone *string `json:"phone,omitempty"` + + // UserStatus User Status + UserStatus *int32 `json:"userStatus,omitempty"` + Username *string `json:"username,omitempty"` +} + +// UserArray defines model for UserArray. +type UserArray = []User + +// FindPetsByStatusParams defines parameters for FindPetsByStatus. +type FindPetsByStatusParams struct { + // Status Status values that need to be considered for filter + Status *FindPetsByStatusParamsStatus `form:"status,omitempty" json:"status,omitempty"` +} + +// FindPetsByStatusParamsStatus defines parameters for FindPetsByStatus. +type FindPetsByStatusParamsStatus string + +// FindPetsByTagsParams defines parameters for FindPetsByTags. +type FindPetsByTagsParams struct { + // Tags Tags to filter by + Tags *[]string `form:"tags,omitempty" json:"tags,omitempty"` +} + +// DeletePetParams defines parameters for DeletePet. +type DeletePetParams struct { + ApiKey *string `json:"api_key,omitempty"` +} + +// UpdatePetWithFormParams defines parameters for UpdatePetWithForm. +type UpdatePetWithFormParams struct { + // Name Name of pet that needs to be updated + Name *string `form:"name,omitempty" json:"name,omitempty"` + + // Status Status of pet that needs to be updated + Status *string `form:"status,omitempty" json:"status,omitempty"` +} + +// UploadFileParams defines parameters for UploadFile. +type UploadFileParams struct { + // AdditionalMetadata Additional Metadata + AdditionalMetadata *string `form:"additionalMetadata,omitempty" json:"additionalMetadata,omitempty"` +} + +// CreateUsersWithListInputJSONBody defines parameters for CreateUsersWithListInput. +type CreateUsersWithListInputJSONBody = []User + +// LoginUserParams defines parameters for LoginUser. +type LoginUserParams struct { + // Username The user name for login + Username *string `form:"username,omitempty" json:"username,omitempty"` + + // Password The password for login in clear text + Password *string `form:"password,omitempty" json:"password,omitempty"` +} + +// AddPetJSONRequestBody defines body for AddPet for application/json ContentType. +type AddPetJSONRequestBody = Pet + +// AddPetFormdataRequestBody defines body for AddPet for application/x-www-form-urlencoded ContentType. +type AddPetFormdataRequestBody = Pet + +// UpdatePetJSONRequestBody defines body for UpdatePet for application/json ContentType. +type UpdatePetJSONRequestBody = Pet + +// UpdatePetFormdataRequestBody defines body for UpdatePet for application/x-www-form-urlencoded ContentType. +type UpdatePetFormdataRequestBody = Pet + +// PlaceOrderJSONRequestBody defines body for PlaceOrder for application/json ContentType. +type PlaceOrderJSONRequestBody = Order + +// PlaceOrderFormdataRequestBody defines body for PlaceOrder for application/x-www-form-urlencoded ContentType. +type PlaceOrderFormdataRequestBody = Order + +// CreateUserJSONRequestBody defines body for CreateUser for application/json ContentType. +type CreateUserJSONRequestBody = User + +// CreateUserFormdataRequestBody defines body for CreateUser for application/x-www-form-urlencoded ContentType. +type CreateUserFormdataRequestBody = User + +// CreateUsersWithListInputJSONRequestBody defines body for CreateUsersWithListInput for application/json ContentType. +type CreateUsersWithListInputJSONRequestBody = CreateUsersWithListInputJSONBody + +// UpdateUserJSONRequestBody defines body for UpdateUser for application/json ContentType. +type UpdateUserJSONRequestBody = User + +// UpdateUserFormdataRequestBody defines body for UpdateUser for application/x-www-form-urlencoded ContentType. +type UpdateUserFormdataRequestBody = User + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/+xb/2/jNrL/V/jUB/QVcCwn2b69Gjig2c22yF26GzS7dz0kQUFLY4ldiVRJyo4b5H8/", + "DEnJ+mrLTpy7ot0fdrUSyRnOfOYr6QcvEGkmOHCtvOmDJ+HXHJR+I0IG5sUVaPwnEFwDN480yxIWUM0E", + "939RguM7FcSQUnz6Xwlzb+p94a/X9e1X5eNaj4+j2gr3abLrAo8jLwQVSJbhCt4UeSRi9gsEmuiYasIB", + "QkW0IDMgNAwhxGcdA1FaSPAeR94nBfJMSrraaW9MQ6q28YhLIwm9ysCbetRQ6WD6kilNxJzkCqTj3gjH", + "rYNkzsJQgjKPmRQZSO2UEjBtOId7mmYJkrmiiSBniRZeSVlpyXiErChNNdTHvz3rHijB6ns98tXpa3K5", + "0lrwrhm/saw+/JtXp5Pj9si1QNxWR57TPKepkZPbK448y9iPoDLBFXTsXYTm7VzIlGpv6jGuT0/WFBnX", + "EFkVpKAUjczoFuP2xcPOfH7xRQhzmidWWW+phkjIVZtNFtbkcjyqMfz/rzoZtjSq4jwXkdpDmkHBl2Ey", + "V1qkINtM0jXABmG7AGQT3j3KBGR9KWmWQehNtcwB2WlKZoJ/BokHTaUtojnEUb7aR0iFXHDoBxl2SQiF", + "kICuQmUmRAKUe11bGbSNDPRFY+I3f3n9+mTQ5F9zynXT/F+PhpiDill27jxBOTykGo40S6HPceRGDnXf", + "ZYRFru3XkQc8T73pjZclNIDQMw5eioV5DCFhC5AQenejitIqI3ZVmzCawnEuNjU0VjHJTVguTXdvPbah", + "GIooYp2SzGKhxSeZ1G2tPqy50WJOVSYNe2sZV5/GMtDEfiOMV2LhWnl0QVlCZwm+y4CHliMlEqO5tv+k", + "0XC/8ZFG3tA9PI5MEsIQMtMbK4uq/O624SNzWQYS7XHLOyh3V3RqakeZNKBFHlLKkjpmfhEx/9a8Hwci", + "7cLOnEml37fA9jcRd4bk/cCc0E4aNAXViWeq1FLIOinv+OT01dc98OcwcCx6+eseGKNUK36nJ7r2+L/u", + "8KFjMLra3RHlJtMzVgdBLpleXSPeXWjN2M+fwXghhpzHQENDpQiR7vvaKjL2d1i5+GCM82ea69jANRFL", + "i94U01NmU9Vcx0Ky30yyil5i6sVaZ2rq+8UCp2O1pFEEcsyEL3CCX8xCm1KByMBl/TSc4ixvap7JSuSS", + "mBcYwJmG4msqQjZfmU/oSMw4GgQi5y4zL0SGhE7sK7jXKPjkXAQdKv2O8ZCIXJNUSCB0ho/Xlm1v5OXl", + "xqa+v96NwTmfiyJ9p4GumBfKUgNNv61PqNP9GDNFmCKUKAMFglXENYqNXINcgCQzqiAkwrrLDxnws6sL", + "cjqeEJVBwOauThgT8i+Rk4ByMm9v5Za7vRCqyU1rH3f/13r11ZhcWJI6ZjIkTIM0hLBYwNfWlQsJI7KE", + "LxdA1JLpIF5XOSEoFiE3UmliAi0N4v+55QWbXCxJDElGMBikJg6bebi9ZQw6BkmY/lKR2Yqk9DPjEQli", + "yiNQawpzxplhimkFyZwIWXzD/Hx8yz9iIbakqxFZMh0TzDCQX8NAkyjjJAIOkiYjQnlI4D4TCogSKRSb", + "5rAkc6A6l2CA9+Hs+nR8y2/5NQ7KFczzhCSMf1bTW35Ebj7GVYVKyIRiWsiVFTgaScR0nM/Q5xbCP6IZ", + "K58LG/qqXE6JXAaW4cr+57jzKrGdKfizRMz8lCoN0lcy8FPKuC/B0lO+yIDTjI1XNE2+8kZewgJwlZHz", + "JWcZDWIgJ+NJ02KWy+WYmq9jISPfTVX+5cXbd++v3x2djCfjWKeJic4gU/VhjuBnAXRZnW+G+OizmDbO", + "swD3ldsLOaoaijfyFiCVtbfj8WR8/BoJuQ15U+90PBmjr86ojo1rQNdloqZQuu0qzsKQUAMFtIFaTW9W", + "tXaCuTUOxexwVGlorA7bxzhaLpdHGIWOcpkARzMI//OtkbcSqIaK1BrZ3zrVMtkXvrCFt1HHyWTyX9/8", + "uc6DAJRC+y8hgCh7Nfm6jaALvqAJCwnjWe7aLS50e9Obh2bkvanGvlElSt493o08lacpxTpjMy5tonxj", + "0tI7jO55B7I/ZaFREydwz5RGl4tLzVbkImxh2w7+E95tuQHXfzSQT/pBfnFOVI6MQGjHvmqPxbDFhSZz", + "kfOw12z+gevZJATuA7Cvn8t6urHfspzHkYkOPiZZb1brAiGCDnv6IU80w6TO1bwLmuSgTPIxA4LZBwsh", + "tKlJINKUEgUZlVRDSGz+r1pmh4kqxrmSOMYtSVPQIJURQENnNdJlY9r1pQPBFQtBQmhSiDlLtEl44T5L", + "TIcToTqytcOvOcjVunRQBfk1pIqe5LRWxu9V2qNynmQfgzoCBuetJvkWw3nCyi2LUvtYVBVNz4Z/xJUy", + "GRT6+1K5m9D/0TVfNmMfV9gJ8mPySZlpxyP8+8T8fWpTXDCWOd5gFYapLTaBY9AALNzJbDUM8dou3YGE", + "nr5+qfc/wdwLZk2jgyLZKa0bxw+mCf5ouSsa7HVknZv3Ns1pgGpLR2Ut5FZLpyv+MeOTHRsOgFiRrJe2", + "HftmIlEltLXD1kLiRt1gEvO8urHSVIR2BtdRtyv5EXQuuemRMB4l4ObW9fQ96CvQb1ZGQhut/+Icy3mX", + "I0uz9svJ+/eQ5qkXSvOamCqblTd3aCJPMP+ybDnvKn1cVd9TzvyT6fg7IdMdYNQ87c/NWuHhUNVyIO+p", + "bVINYacR1dzRxg7eyqWU+5HrSBu3pX4vWENbFDj/VCvgbMqCqiEh1XRbSPHzLBE0vEjdyX8f6HDQd8zm", + "xIOdlpXwC8LrLAxNn5Em5AfQ1AmgS720HFkZuEXVQ3oHItCgj5SWQNO6gyu3M2Ocyq4T+MdD1trVWyKD", + "fekzodViTBHTzEagdYHSdncZXwDX7kB6S5RNaYZgc2VGIELbcndn/gxUV/C9KAk8UdhrBF3VDi0HHK3V", + "D8r21kY1DtXkXYjI+ga7XwbdBZPt+lRVIMq7HZ1t5auEBkWL1Axt9o/qIjfD7XWRw/TfPpQXHJ6xA9ez", + "6C5JS7HEQa26JDI8NxoWo0osOXVzp2usbptJcQ+E/AfzT6tuaRxoCkks9UJIRMsVcdZCLs6VjWgmvye3", + "+WRyGpDjyWQyJmd8pWPGI0JnYgHmJRGScMHdbJyaJO60TNvDKJBSyLZnsAl/gdMBIQ4Rb0XSSizsVnvS", + "KieTlyyPhqS89o5SI+ltlEMky2UQU1VsvJm4FjjoKY/20fRfydeoUvMf1PCYfDAHrq5NWFdv2WxV4y7P", + "b7Y4vPDqUe4czOnxSyr3EA7jORzbi9Vjm8FpC6lh0EQXlatN4c1cdAjQ3/FkZaxZcDANmhhIIqII0E+a", + "e8htlNnzQ3dB5hDBzl6Xft5Y173mLoj4pLoBYcUR1i9tN8Bd9uIPKqHn2E1/UlZC0Z0f5+6GlAOf+e8a", + "e35gRmH9fsnUhgN8u5oiSeXuu/OREVsAt/GaFBcE+6CoCkoXJrzvD8wnXeg/ZBZ0OJ33nSRWULsnSAar", + "tQ9FiYgYr1RJdfVf4lfniDafMcQWrwRDmEnv7MLdZXN5KXCnRgzSKK5ArkmgGw0SoJJouNc9BMubk7s0", + "YnbFVvsq40Ygter2QZ5i5FrwhsOfjt7dZ0yCOjqbaxuO6kuYQ17GyaePb8kyBk60+AycgJ3ldSYUGy6n", + "P468n45+xO+XLGUduA1okmDBKElsLikmiVhCWMQ959C6k5jOItdIZWPgL6Dkl8hY5wE1e7kUkbIQZby4", + "LbJSGtLNxiHszZE+6xC5LuN0dzja2bANoyLXJMilBK4b6QJRoJRFQh/bD4VQNpZMT0pRbCo/1DMYp7Bb", + "gVNxEf1J8PZW6gDkbE8czQXoLUVNd7AedQPnezCoebN67y7a7ydAV0TYE2SkeFw7NiYHlOzvI+7uVUo8", + "Ey6+B23tdbZaR8YuhHTeTXuScdq2/hDjrOPqsHb5Ry9jOq7NuXBU/5nsswUSR7CnjDBtYLkocGFvMPs0", + "Y/7i1EONuQlNwu8WINcNs1zbnyNc2d79Dr862Pw7g+rPitrnM2briNbyCrSp13fkwPGP7BdN5+0cFWpq", + "NRcKJSi3rBN77Wcrd4//DgAA///gocX++z0AAA==", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + for rawPath, rawFunc := range externalRef0.PathToRawSpec(path.Join(path.Dir(pathToFile), "../packageB/spec.yaml")) { + if _, ok := res[rawPath]; ok { + // it is not possible to compare functions in golang, so always overwrite the old value + } + res[rawPath] = rawFunc + } + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/externalref/petstore/spec.yaml b/internal/test/externalref/petstore/spec.yaml new file mode 100644 index 0000000000..2d1dcd41d5 --- /dev/null +++ b/internal/test/externalref/petstore/spec.yaml @@ -0,0 +1 @@ +{"openapi":"3.0.2","info":{"title":"Swagger Petstore - OpenAPI 3.0","description":"This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about\nSwagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!\nYou can now help us improve the API whether it's by making changes to the definition itself or to the code.\nThat way, with time, we can improve the API in general, and expose some of the new features in OAS3.\n\nSome useful links:\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\n- [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)","termsOfService":"http://swagger.io/terms/","contact":{"email":"apiteam@swagger.io"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"version":"1.0.17"},"externalDocs":{"description":"Find out more about Swagger","url":"http://swagger.io"},"servers":[{"url":"/api/v3"}],"tags":[{"name":"pet","description":"Everything about your Pets","externalDocs":{"description":"Find out more","url":"http://swagger.io"}},{"name":"store","description":"Access to Petstore orders","externalDocs":{"description":"Find out more about our store","url":"http://swagger.io"}},{"name":"user","description":"Operations about user"}],"paths":{"/pet":{"put":{"tags":["pet"],"summary":"Update an existing pet","description":"Update an existing pet by Id","operationId":"updatePet","requestBody":{"description":"Update an existent pet in the store","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Pet"}},"application/xml":{"schema":{"$ref":"#/components/schemas/Pet"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Pet"}}},"required":true},"responses":{"200":{"description":"Successful operation","content":{"application/xml":{"schema":{"$ref":"#/components/schemas/Pet"}},"application/json":{"schema":{"$ref":"#/components/schemas/Pet"}}}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"},"405":{"description":"Validation exception"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]},"post":{"tags":["pet"],"summary":"Add a new pet to the store","description":"Add a new pet to the store","operationId":"addPet","requestBody":{"description":"Create a new pet in the store","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Pet"}},"application/xml":{"schema":{"$ref":"#/components/schemas/Pet"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Pet"}}},"required":true},"responses":{"200":{"description":"Successful operation","content":{"application/xml":{"schema":{"$ref":"#/components/schemas/Pet"}},"application/json":{"schema":{"$ref":"#/components/schemas/Pet"}}}},"405":{"description":"Invalid input"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByStatus":{"get":{"tags":["pet"],"summary":"Finds Pets by status","description":"Multiple status values can be provided with comma separated strings","operationId":"findPetsByStatus","parameters":[{"name":"status","in":"query","description":"Status values that need to be considered for filter","required":false,"explode":true,"schema":{"type":"string","default":"available","enum":["available","pending","sold"]}}],"responses":{"200":{"description":"successful operation","content":{"application/xml":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Pet"}}},"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Pet"}}}}},"400":{"description":"Invalid status value"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByTags":{"get":{"tags":["pet"],"summary":"Finds Pets by tags","description":"Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.","operationId":"findPetsByTags","parameters":[{"name":"tags","in":"query","description":"Tags to filter by","required":false,"explode":true,"schema":{"type":"array","items":{"type":"string"}}}],"responses":{"200":{"description":"successful operation","content":{"application/xml":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Pet"}}},"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Pet"}}}}},"400":{"description":"Invalid tag value"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/{petId}":{"get":{"tags":["pet"],"summary":"Find pet by ID","description":"Returns a single pet","operationId":"getPetById","parameters":[{"name":"petId","in":"path","description":"ID of pet to return","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"successful operation","content":{"application/xml":{"schema":{"$ref":"#/components/schemas/Pet"}},"application/json":{"schema":{"$ref":"#/components/schemas/Pet"}}}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"}},"security":[{"api_key":[]},{"petstore_auth":["write:pets","read:pets"]}]},"post":{"tags":["pet"],"summary":"Updates a pet in the store with form data","description":"","operationId":"updatePetWithForm","parameters":[{"name":"petId","in":"path","description":"ID of pet that needs to be updated","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"name","in":"query","description":"Name of pet that needs to be updated","schema":{"type":"string"}},{"name":"status","in":"query","description":"Status of pet that needs to be updated","schema":{"type":"string"}}],"responses":{"405":{"description":"Invalid input"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]},"delete":{"tags":["pet"],"summary":"Deletes a pet","description":"","operationId":"deletePet","parameters":[{"name":"api_key","in":"header","description":"","required":false,"schema":{"type":"string"}},{"name":"petId","in":"path","description":"Pet id to delete","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"400":{"description":"Invalid pet value"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/{petId}/uploadImage":{"post":{"tags":["pet"],"summary":"uploads an image","description":"","operationId":"uploadFile","parameters":[{"name":"petId","in":"path","description":"ID of pet to update","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"additionalMetadata","in":"query","description":"Additional Metadata","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"responses":{"200":{"description":"successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse"}}}}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/store/inventory":{"get":{"tags":["store"],"summary":"Returns pet inventories by status","description":"Returns a map of status codes to quantities","operationId":"getInventory","responses":{"200":{"description":"successful operation","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"integer","format":"int32"}}}}}},"security":[{"api_key":[]}]}},"/store/order":{"post":{"tags":["store"],"summary":"Place an order for a pet","description":"Place a new order in the store","operationId":"placeOrder","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}},"application/xml":{"schema":{"$ref":"#/components/schemas/Order"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Order"}}}},"responses":{"200":{"description":"successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"405":{"description":"Invalid input"}}}},"/store/order/{orderId}":{"get":{"tags":["store"],"summary":"Find purchase order by ID","description":"For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.","operationId":"getOrderById","parameters":[{"name":"orderId","in":"path","description":"ID of order that needs to be fetched","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"successful operation","content":{"application/xml":{"schema":{"$ref":"#/components/schemas/Order"}},"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}}},"delete":{"tags":["store"],"summary":"Delete purchase order by ID","description":"For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors","operationId":"deleteOrder","parameters":[{"name":"orderId","in":"path","description":"ID of the order that needs to be deleted","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}}}},"/user":{"post":{"tags":["user"],"summary":"Create user","description":"This can only be done by the logged in user.","operationId":"createUser","requestBody":{"description":"Created user object","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}},"application/xml":{"schema":{"$ref":"#/components/schemas/User"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/User"}}}},"responses":{"default":{"description":"successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}},"application/xml":{"schema":{"$ref":"#/components/schemas/User"}}}}}}},"/user/createWithList":{"post":{"tags":["user"],"summary":"Creates list of users with given input array","description":"Creates list of users with given input array","operationId":"createUsersWithListInput","requestBody":{"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/User"}}}}},"responses":{"200":{"description":"Successful operation","content":{"application/xml":{"schema":{"$ref":"#/components/schemas/User"}},"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"default":{"description":"successful operation"}}}},"/user/login":{"get":{"tags":["user"],"summary":"Logs user into the system","description":"","operationId":"loginUser","parameters":[{"name":"username","in":"query","description":"The user name for login","required":false,"schema":{"type":"string"}},{"name":"password","in":"query","description":"The password for login in clear text","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"successful operation","headers":{"X-Rate-Limit":{"description":"calls per hour allowed by the user","schema":{"type":"integer","format":"int32"}},"X-Expires-After":{"description":"date in UTC when token expires","schema":{"type":"string","format":"date-time"}}},"content":{"application/xml":{"schema":{"type":"string"}},"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid username/password supplied"}}}},"/user/logout":{"get":{"tags":["user"],"summary":"Logs out current logged in user session","description":"","operationId":"logoutUser","parameters":[],"responses":{"default":{"description":"successful operation"}}}},"/user/{username}":{"get":{"tags":["user"],"summary":"Get user by user name","description":"","operationId":"getUserByName","parameters":[{"name":"username","in":"path","description":"The name that needs to be fetched. Use user1 for testing. ","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"successful operation","content":{"application/xml":{"schema":{"$ref":"#/components/schemas/User"}},"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}}},"put":{"tags":["user"],"summary":"Update user","description":"This can only be done by the logged in user.","operationId":"updateUser","parameters":[{"name":"username","in":"path","description":"name that need to be deleted","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Update an existent user in the store","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}},"application/xml":{"schema":{"$ref":"#/components/schemas/User"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/User"}}}},"responses":{"default":{"description":"successful operation"}}},"delete":{"tags":["user"],"summary":"Delete user","description":"This can only be done by the logged in user.","operationId":"deleteUser","parameters":[{"name":"username","in":"path","description":"The name that needs to be deleted","required":true,"schema":{"type":"string"}}],"responses":{"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}}}}},"components":{"schemas":{"Order":{"type":"object","properties":{"id":{"type":"integer","format":"int64","example":10},"petId":{"type":"integer","format":"int64","example":198772},"quantity":{"type":"integer","format":"int32","example":7},"shipDate":{"type":"string","format":"date-time"},"status":{"type":"string","description":"Order Status","example":"approved","enum":["placed","approved","delivered"]},"complete":{"type":"boolean"}},"xml":{"name":"order"}},"Customer":{"type":"object","properties":{"id":{"type":"integer","format":"int64","example":100000},"username":{"type":"string","example":"fehguy"},"address":{"type":"array","xml":{"name":"addresses","wrapped":true},"items":{"$ref":"#/components/schemas/Address"}}},"xml":{"name":"customer"}},"Address":{"type":"object","properties":{"street":{"type":"string","example":"437 Lytton"},"city":{"type":"string","example":"Palo Alto"},"state":{"type":"string","example":"CA"},"zip":{"type":"string","example":"94301"}},"xml":{"name":"address"}},"Category":{"type":"object","properties":{"id":{"type":"integer","format":"int64","example":1},"name":{"type":"string","example":"Dogs"}},"xml":{"name":"category"}},"User":{"type":"object","properties":{"id":{"type":"integer","format":"int64","example":10},"username":{"type":"string","example":"theUser"},"firstName":{"type":"string","example":"John"},"lastName":{"type":"string","example":"James"},"email":{"type":"string","example":"john@email.com"},"password":{"type":"string","example":"12345"},"phone":{"type":"string","example":"12345"},"userStatus":{"type":"integer","description":"User Status","format":"int32","example":1}},"xml":{"name":"user"}},"Tag":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"}},"xml":{"name":"tag"}},"Pet":{"required":["name","photoUrls"],"type":"object","properties":{"id":{"type":"integer","format":"int64","example":10},"name":{"type":"string","example":"doggie"},"category":{"$ref":"#/components/schemas/Category"},"photoUrls":{"type":"array","xml":{"wrapped":true},"items":{"type":"string","xml":{"name":"photoUrl"}}},"tags":{"type":"array","xml":{"wrapped":true},"items":{"$ref":"#/components/schemas/Tag"}},"status":{"type":"string","description":"pet status in the store","enum":["available","pending","sold"]}},"xml":{"name":"pet"}},"ApiResponse":{"type":"object","properties":{"code":{"type":"integer","format":"int32"},"type":{"type":"string"},"message":{"type":"string"}},"xml":{"name":"##default"}}},"requestBodies":{"Pet":{"description":"Pet object that needs to be added to the store","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Pet"}},"application/xml":{"schema":{"$ref":"#/components/schemas/Pet"}}}},"UserArray":{"description":"List of user object","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/User"}}}}}},"securitySchemes":{"petstore_auth":{"type":"oauth2","flows":{"implicit":{"authorizationUrl":"https://petstore3.swagger.io/oauth/authorize","scopes":{"write:pets":"modify pets in your account","read:pets":"read your pets"}}}},"api_key":{"type":"apiKey","name":"api_key","in":"header"}}}} \ No newline at end of file diff --git a/internal/test/externalref/spec.yaml b/internal/test/externalref/spec.yaml index ba0ae6555f..8f924b75c4 100644 --- a/internal/test/externalref/spec.yaml +++ b/internal/test/externalref/spec.yaml @@ -11,3 +11,5 @@ components: $ref: ./packageB/spec.yaml#/components/schemas/ObjectB object_c: $ref: ./object_c.json + pet: + $ref: https://petstore3.swagger.io/api/v3/openapi.json#/components/schemas/Pet diff --git a/internal/test/filter/doc.go b/internal/test/filter/doc.go new file mode 100644 index 0000000000..a9051b1015 --- /dev/null +++ b/internal/test/filter/doc.go @@ -0,0 +1,4 @@ +package client + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --package=filtertags --generate=server -o tags/server.gen.go -include-tags included-tag1,included-tag2 server.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --package=filteroperations --generate=server -o operations/server.gen.go -include-operation-ids included-operation1,included-operation2 server.yaml diff --git a/internal/test/filter/operations/server.gen.go b/internal/test/filter/operations/server.gen.go new file mode 100644 index 0000000000..49fc69b5e5 --- /dev/null +++ b/internal/test/filter/operations/server.gen.go @@ -0,0 +1,74 @@ +// Package filteroperations provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package filteroperations + +import ( + "github.com/labstack/echo/v4" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /included1) + IncludedOperation1(ctx echo.Context) error + + // (GET /included2) + IncludedOperation2(ctx echo.Context) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// IncludedOperation1 converts echo context to params. +func (w *ServerInterfaceWrapper) IncludedOperation1(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.IncludedOperation1(ctx) + return err +} + +// IncludedOperation2 converts echo context to params. +func (w *ServerInterfaceWrapper) IncludedOperation2(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.IncludedOperation2(ctx) + return err +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.GET(baseURL+"/included1", wrapper.IncludedOperation1) + router.GET(baseURL+"/included2", wrapper.IncludedOperation2) + +} diff --git a/internal/test/filter/operations/server_test.go b/internal/test/filter/operations/server_test.go new file mode 100644 index 0000000000..2dc33642b3 --- /dev/null +++ b/internal/test/filter/operations/server_test.go @@ -0,0 +1,26 @@ +package filteroperations + +import ( + "testing" + + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" +) + +type server struct{} + +func (s server) IncludedOperation1(ctx echo.Context) error { + return nil +} + +func (s server) IncludedOperation2(ctx echo.Context) error { + return nil +} + +func TestServer(t *testing.T) { + assert := assert.New(t) + + var s ServerInterface = server{} + assert.NoError(s.IncludedOperation1(nil)) + assert.NoError(s.IncludedOperation2(nil)) +} diff --git a/internal/test/filter/server.yaml b/internal/test/filter/server.yaml new file mode 100644 index 0000000000..63e0a536ad --- /dev/null +++ b/internal/test/filter/server.yaml @@ -0,0 +1,48 @@ +openapi: "3.0.1" +info: + version: 1.0.0 + title: Test Server + license: + name: MIT + description: | + This tests whether filtering works correctly +paths: + /included1: + get: + operationId: included-operation1 + tags: + - included-tag1 + responses: + 200: + application/json: + schema: + $ref: '#/components/schemas/SchemaObject' + /filtered: + get: + operationId: filtered-operation + tags: + - filtered-tag + responses: + 200: + application/json: + schema: + $ref: '#/components/schemas/SchemaObject' + /included2: + get: + operationId: included-operation2 + tags: + - included-tag2 + responses: + 200: + application/json: + schema: + $ref: '#/components/schemas/SchemaObject' + +components: + schemas: + SchemaObject: + properties: + firstName: + type: string + required: + - firstName diff --git a/internal/test/filter/tags/server.gen.go b/internal/test/filter/tags/server.gen.go new file mode 100644 index 0000000000..8137c0f39c --- /dev/null +++ b/internal/test/filter/tags/server.gen.go @@ -0,0 +1,74 @@ +// Package filtertags provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package filtertags + +import ( + "github.com/labstack/echo/v4" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /included1) + IncludedOperation1(ctx echo.Context) error + + // (GET /included2) + IncludedOperation2(ctx echo.Context) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// IncludedOperation1 converts echo context to params. +func (w *ServerInterfaceWrapper) IncludedOperation1(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.IncludedOperation1(ctx) + return err +} + +// IncludedOperation2 converts echo context to params. +func (w *ServerInterfaceWrapper) IncludedOperation2(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.IncludedOperation2(ctx) + return err +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.GET(baseURL+"/included1", wrapper.IncludedOperation1) + router.GET(baseURL+"/included2", wrapper.IncludedOperation2) + +} diff --git a/internal/test/filter/tags/server_test.go b/internal/test/filter/tags/server_test.go new file mode 100644 index 0000000000..1f466269cf --- /dev/null +++ b/internal/test/filter/tags/server_test.go @@ -0,0 +1,26 @@ +package filtertags + +import ( + "testing" + + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" +) + +type server struct{} + +func (s server) IncludedOperation1(ctx echo.Context) error { + return nil +} + +func (s server) IncludedOperation2(ctx echo.Context) error { + return nil +} + +func TestServer(t *testing.T) { + assert := assert.New(t) + + var s ServerInterface = server{} + assert.NoError(s.IncludedOperation1(nil)) + assert.NoError(s.IncludedOperation2(nil)) +} diff --git a/internal/test/go.mod b/internal/test/go.mod new file mode 100644 index 0000000000..6f70f67ca3 --- /dev/null +++ b/internal/test/go.mod @@ -0,0 +1,114 @@ +module github.com/oapi-codegen/oapi-codegen/v2/internal/test + +go 1.24.3 + +replace github.com/oapi-codegen/oapi-codegen/v2 => ../../ + +require ( + github.com/getkin/kin-openapi v0.133.0 + github.com/gin-gonic/gin v1.11.0 + github.com/go-chi/chi/v5 v5.2.5 + github.com/gofiber/fiber/v2 v2.52.12 + github.com/google/uuid v1.6.0 + github.com/gorilla/mux v1.8.1 + github.com/kataras/iris/v12 v12.2.11 + github.com/labstack/echo/v4 v4.15.1 + github.com/oapi-codegen/nullable v1.1.0 + github.com/oapi-codegen/oapi-codegen/v2 v2.0.0-00010101000000-000000000000 + github.com/oapi-codegen/runtime v1.2.0 + github.com/oapi-codegen/testutil v1.1.0 + github.com/stretchr/testify v1.11.1 + gopkg.in/yaml.v2 v2.4.0 +) + +require ( + github.com/BurntSushi/toml v1.3.2 // indirect + github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect + github.com/CloudyKit/jet/v6 v6.2.0 // indirect + github.com/Joker/jade v1.1.3 // indirect + github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/aymerick/douceur v0.2.0 // indirect + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect + github.com/fatih/structs v1.1.0 // indirect + github.com/flosch/pongo2/v4 v4.0.2 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0 // indirect + github.com/gorilla/css v1.0.0 // indirect + github.com/iris-contrib/schema v0.0.6 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kataras/blocks v0.0.8 // indirect + github.com/kataras/golog v0.1.11 // indirect + github.com/kataras/pio v0.0.13 // indirect + github.com/kataras/sitemap v0.0.6 // indirect + github.com/kataras/tunnel v0.0.4 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mailgun/raymond/v2 v2.0.48 // indirect + github.com/mailru/easyjson v0.9.1 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/microcosm-cc/bluemonday v1.0.26 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.54.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/schollz/closestmatch v2.1.0+incompatible // indirect + github.com/sirupsen/logrus v1.9.1 // indirect + github.com/speakeasy-api/jsonpath v0.6.0 // indirect + github.com/speakeasy-api/openapi-overlay v0.10.3 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/tdewolff/minify/v2 v2.20.19 // indirect + github.com/tdewolff/parse/v2 v2.7.12 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.51.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect + github.com/yosssi/ace v0.0.5 // indirect + go.uber.org/mock v0.5.0 // indirect + golang.org/x/arch v0.20.0 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/net v0.50.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.42.0 // indirect + google.golang.org/protobuf v1.36.9 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/internal/test/go.sum b/internal/test/go.sum new file mode 100644 index 0000000000..d4d3ba66ee --- /dev/null +++ b/internal/test/go.sum @@ -0,0 +1,392 @@ +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= +github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME= +github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= +github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc= +github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= +github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk= +github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0= +github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM= +github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= +github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= +github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/gofiber/fiber/v2 v2.52.12 h1:0LdToKclcPOj8PktUdIKo9BUohjjwfnQl42Dhw8/WUw= +github.com/gofiber/fiber/v2 v2.52.12/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0 h1:4gjrh/PN2MuWCCElk8/I4OCKRKWCCo2zEct3VKCbibU= +github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/iris-contrib/httpexpect/v2 v2.15.2 h1:T9THsdP1woyAqKHwjkEsbCnMefsAFvk8iJJKokcJ3Go= +github.com/iris-contrib/httpexpect/v2 v2.15.2/go.mod h1:JLDgIqnFy5loDSUv1OA2j0mb6p/rDhiCqigP22Uq9xE= +github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw= +github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/kataras/blocks v0.0.8 h1:MrpVhoFTCR2v1iOOfGng5VJSILKeZZI+7NGfxEh3SUM= +github.com/kataras/blocks v0.0.8/go.mod h1:9Jm5zx6BB+06NwA+OhTbHW1xkMOYxahnqTN5DveZ2Yg= +github.com/kataras/golog v0.1.11 h1:dGkcCVsIpqiAMWTlebn/ZULHxFvfG4K43LF1cNWSh20= +github.com/kataras/golog v0.1.11/go.mod h1:mAkt1vbPowFUuUGvexyQ5NFW6djEgGyxQBIARJ0AH4A= +github.com/kataras/iris/v12 v12.2.11 h1:sGgo43rMPfzDft8rjVhPs6L3qDJy3TbBrMD/zGL1pzk= +github.com/kataras/iris/v12 v12.2.11/go.mod h1:uMAeX8OqG9vqdhyrIPv8Lajo/wXTtAF43wchP9WHt2w= +github.com/kataras/pio v0.0.13 h1:x0rXVX0fviDTXOOLOmr4MUxOabu1InVSTu5itF8CXCM= +github.com/kataras/pio v0.0.13/go.mod h1:k3HNuSw+eJ8Pm2lA4lRhg3DiCjVgHlP8hmXApSej3oM= +github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY= +github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4= +github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA= +github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/labstack/echo/v4 v4.15.1 h1:S9keusg26gZpjMmPqB5hOEvNKnmd1lNmcHrbbH2lnFs= +github.com/labstack/echo/v4 v4.15.1/go.mod h1:xmw1clThob0BSVRX1CRQkGQ/vjwcpOMjQZSZa9fKA/c= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= +github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= +github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/oapi-codegen/nullable v1.1.0 h1:eAh8JVc5430VtYVnq00Hrbpag9PFRGWLjxR1/3KntMs= +github.com/oapi-codegen/nullable v1.1.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY= +github.com/oapi-codegen/runtime v1.2.0 h1:RvKc1CVS1QeKSNzO97FBQbSMZyQ8s6rZd+LpmzwHMP4= +github.com/oapi-codegen/runtime v1.2.0/go.mod h1:Y7ZhmmlE8ikZOmuHRRndiIm7nf3xcVv+YMweKgG1DT0= +github.com/oapi-codegen/testutil v1.1.0 h1:EufqpNg43acR3qzr3ObhXmWg3Sl2kwtRnUN5GYY4d5g= +github.com/oapi-codegen/testutil v1.1.0/go.mod h1:ttCaYbHvJtHuiyeBF0tPIX+4uhEPTeizXKx28okijLw= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= +github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= +github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= +github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.1 h1:Ou41VVR3nMWWmTiEUnj0OlsgOSCUFgsPAOl6jRIcVtQ= +github.com/sirupsen/logrus v1.9.1/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8= +github.com/speakeasy-api/jsonpath v0.6.0/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw= +github.com/speakeasy-api/openapi-overlay v0.10.3 h1:70een4vwHyslIp796vM+ox6VISClhtXsCjrQNhxwvWs= +github.com/speakeasy-api/openapi-overlay v0.10.3/go.mod h1:RJjV0jbUHqXLS0/Mxv5XE7LAnJHqHw+01RDdpoGqiyY= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tdewolff/minify/v2 v2.20.19 h1:tX0SR0LUrIqGoLjXnkIzRSIbKJ7PaNnSENLD4CyH6Xo= +github.com/tdewolff/minify/v2 v2.20.19/go.mod h1:ulkFoeAVWMLEyjuDz1ZIWOA31g5aWOawCFRp9R/MudM= +github.com/tdewolff/parse/v2 v2.7.12 h1:tgavkHc2ZDEQVKy1oWxwIyh5bP4F5fEh/JmBwPP/3LQ= +github.com/tdewolff/parse/v2 v2.7.12/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA= +github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= +github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo= +github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= +github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= +github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= +github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8= +golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= +moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= diff --git a/internal/test/issues/issue-1039/client-config.yaml b/internal/test/issues/issue-1039/client-config.yaml new file mode 100644 index 0000000000..a117c7c745 --- /dev/null +++ b/internal/test/issues/issue-1039/client-config.yaml @@ -0,0 +1,5 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: issue1039 +generate: + client: true +output: client.gen.go diff --git a/internal/test/issues/issue-1039/client.gen.go b/internal/test/issues/issue-1039/client.gen.go new file mode 100644 index 0000000000..3114f29862 --- /dev/null +++ b/internal/test/issues/issue-1039/client.gen.go @@ -0,0 +1,261 @@ +// Package issue1039 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue1039 + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" +) + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // ExamplePatchWithBody request with any body + ExamplePatchWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + ExamplePatch(ctx context.Context, body ExamplePatchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) ExamplePatchWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewExamplePatchRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ExamplePatch(ctx context.Context, body ExamplePatchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewExamplePatchRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewExamplePatchRequest calls the generic ExamplePatch builder with application/json body +func NewExamplePatchRequest(server string, body ExamplePatchJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewExamplePatchRequestWithBody(server, "application/json", bodyReader) +} + +// NewExamplePatchRequestWithBody generates requests for ExamplePatch with any type of body +func NewExamplePatchRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/example") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PATCH", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // ExamplePatchWithBodyWithResponse request with any body + ExamplePatchWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ExamplePatchResponse, error) + + ExamplePatchWithResponse(ctx context.Context, body ExamplePatchJSONRequestBody, reqEditors ...RequestEditorFn) (*ExamplePatchResponse, error) +} + +type ExamplePatchResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r ExamplePatchResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ExamplePatchResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ExamplePatchWithBodyWithResponse request with arbitrary body returning *ExamplePatchResponse +func (c *ClientWithResponses) ExamplePatchWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ExamplePatchResponse, error) { + rsp, err := c.ExamplePatchWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseExamplePatchResponse(rsp) +} + +func (c *ClientWithResponses) ExamplePatchWithResponse(ctx context.Context, body ExamplePatchJSONRequestBody, reqEditors ...RequestEditorFn) (*ExamplePatchResponse, error) { + rsp, err := c.ExamplePatch(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseExamplePatchResponse(rsp) +} + +// ParseExamplePatchResponse parses an HTTP response from a ExamplePatchWithResponse call +func ParseExamplePatchResponse(rsp *http.Response) (*ExamplePatchResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ExamplePatchResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} diff --git a/internal/test/issues/issue-1039/defaultbehaviour/defaultbehaviour_test.go b/internal/test/issues/issue-1039/defaultbehaviour/defaultbehaviour_test.go new file mode 100644 index 0000000000..d49813ccf2 --- /dev/null +++ b/internal/test/issues/issue-1039/defaultbehaviour/defaultbehaviour_test.go @@ -0,0 +1,54 @@ +package defaultbehaviour + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func ptr[T any](v T) *T { + return &v +} + +func TestNullableDisabled(t *testing.T) { + // include all fields in patch request + patchReq := PatchRequest{ + ComplexRequiredNullable: &ComplexRequiredNullable{ + Name: ptr("test-name"), + }, + SimpleOptionalNonNullable: ptr(SimpleOptionalNonNullable("bar")), + ComplexOptionalNullable: &ComplexOptionalNullable{ + AliasName: ptr("foo-alias"), + Name: ptr("foo"), + }, + SimpleOptionalNullable: ptr(SimpleOptionalNullable(10)), + SimpleRequiredNullable: ptr(SimpleRequiredNullable(5)), + } + + expected := []byte(`{"complex_optional_nullable":{"alias_name":"foo-alias","name":"foo"},"complex_required_nullable":{"name":"test-name"},"simple_optional_non_nullable":"bar","simple_optional_nullable":10,"simple_required_nullable":5}`) + + actual, err := json.Marshal(patchReq) + require.NoError(t, err) + require.Equal(t, string(expected), string(actual)) + + // omit some fields + patchReq = PatchRequest{ + ComplexRequiredNullable: &ComplexRequiredNullable{ + Name: ptr("test-name"), + }, + // SimpleOptionalNonNullable is omitted + ComplexOptionalNullable: &ComplexOptionalNullable{ + AliasName: ptr("test-alias-name"), + Name: ptr("test-name"), + }, + SimpleOptionalNullable: ptr(SimpleOptionalNullable(10)), + // SimpleRequiredNullable is omitted + } + + expected = []byte(`{"complex_optional_nullable":{"alias_name":"test-alias-name","name":"test-name"},"complex_required_nullable":{"name":"test-name"},"simple_optional_nullable":10,"simple_required_nullable":null}`) + + actual, err = json.Marshal(patchReq) + require.NoError(t, err) + require.Equal(t, string(expected), string(actual)) +} diff --git a/internal/test/issues/issue-1039/defaultbehaviour/types.gen.go b/internal/test/issues/issue-1039/defaultbehaviour/types.gen.go new file mode 100644 index 0000000000..5ed98d88f1 --- /dev/null +++ b/internal/test/issues/issue-1039/defaultbehaviour/types.gen.go @@ -0,0 +1,49 @@ +// Package defaultbehaviour provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package defaultbehaviour + +// PatchRequest A request to patch an existing user object. +type PatchRequest struct { + // ComplexOptionalNullable Complex, optional and nullable + ComplexOptionalNullable *ComplexOptionalNullable `json:"complex_optional_nullable,omitempty"` + + // ComplexRequiredNullable Complex required and nullable + ComplexRequiredNullable *ComplexRequiredNullable `json:"complex_required_nullable"` + + // SimpleOptionalNonNullable Simple optional and non nullable + SimpleOptionalNonNullable *SimpleOptionalNonNullable `json:"simple_optional_non_nullable,omitempty"` + + // SimpleOptionalNullable Simple optional and nullable + SimpleOptionalNullable *SimpleOptionalNullable `json:"simple_optional_nullable,omitempty"` + + // SimpleRequiredNullable Simple required and nullable + SimpleRequiredNullable *SimpleRequiredNullable `json:"simple_required_nullable"` +} + +// ComplexOptionalNullable Complex, optional and nullable +type ComplexOptionalNullable struct { + // AliasName Optional and nullable + AliasName *string `json:"alias_name,omitempty"` + + // Name Optional and non nullable + Name *string `json:"name,omitempty"` +} + +// ComplexRequiredNullable Complex required and nullable +type ComplexRequiredNullable struct { + // Name Optional and non nullable + Name *string `json:"name,omitempty"` +} + +// SimpleOptionalNonNullable Simple optional and non nullable +type SimpleOptionalNonNullable = string + +// SimpleOptionalNullable Simple optional and nullable +type SimpleOptionalNullable = int + +// SimpleRequiredNullable Simple required and nullable +type SimpleRequiredNullable = int + +// ExamplePatchJSONRequestBody defines body for ExamplePatch for application/json ContentType. +type ExamplePatchJSONRequestBody = PatchRequest diff --git a/internal/test/issues/issue-1039/doc.go b/internal/test/issues/issue-1039/doc.go new file mode 100644 index 0000000000..2997380aaf --- /dev/null +++ b/internal/test/issues/issue-1039/doc.go @@ -0,0 +1,6 @@ +package issue1039 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=types-config.yaml spec.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=type-config-defaultbehaviour.yaml spec.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=client-config.yaml spec.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=server-config.yaml spec.yaml diff --git a/internal/test/issues/issue-1039/issue_test.go b/internal/test/issues/issue-1039/issue_test.go new file mode 100644 index 0000000000..1cd390c315 --- /dev/null +++ b/internal/test/issues/issue-1039/issue_test.go @@ -0,0 +1,197 @@ +package issue1039 + +import ( + _ "embed" + "encoding/json" + "testing" + + "github.com/oapi-codegen/nullable" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func ptr[T any](v T) *T { + return &v +} + +func TestNullableTypesMarshal(t *testing.T) { + // include all fields in patch request + patchReq := PatchRequest{ + ComplexRequiredNullable: nullable.NewNullableWithValue(ComplexRequiredNullable{ + Name: ptr("test-name"), + }), + SimpleOptionalNonNullable: ptr(SimpleOptionalNonNullable("bar")), + ComplexOptionalNullable: nullable.NewNullableWithValue(ComplexOptionalNullable{ + AliasName: nullable.NewNullableWithValue("foo-alias"), + Name: ptr("foo"), + }), + SimpleOptionalNullable: nullable.NewNullableWithValue(10), + SimpleRequiredNullable: nullable.NewNullableWithValue(5), + } + + expected := []byte(`{"complex_optional_nullable":{"alias_name":"foo-alias","name":"foo"},"complex_required_nullable":{"name":"test-name"},"simple_optional_non_nullable":"bar","simple_optional_nullable":10,"simple_required_nullable":5}`) + + actual, err := json.Marshal(patchReq) + require.NoError(t, err) + require.Equal(t, string(expected), string(actual)) + + // omit some fields + patchReq = PatchRequest{ + ComplexRequiredNullable: nullable.NewNullableWithValue(ComplexRequiredNullable{ + Name: ptr("test-name"), + }), + // SimpleOptionalNonNullable is omitted + ComplexOptionalNullable: nullable.NewNullableWithValue(ComplexOptionalNullable{ + AliasName: nullable.NewNullableWithValue("test-alias-name"), + Name: ptr("test-name"), + }), + SimpleOptionalNullable: nullable.NewNullableWithValue(10), + // SimpleRequiredNullable is omitted + } + + expected = []byte(`{"complex_optional_nullable":{"alias_name":"test-alias-name","name":"test-name"},"complex_required_nullable":{"name":"test-name"},"simple_optional_nullable":10,"simple_required_nullable":0}`) + + actual, err = json.Marshal(patchReq) + require.NoError(t, err) + require.Equal(t, string(expected), string(actual)) +} + +func TestNullableTypesUnmarshal(t *testing.T) { + type testCase struct { + name string + json []byte + assert func(t *testing.T, obj PatchRequest) + } + tests := []testCase{ + { + name: "when empty json is provided", + json: []byte(`{}`), + assert: func(t *testing.T, obj PatchRequest) { + t.Helper() + + // check for nullable fields + assert.Falsef(t, obj.SimpleRequiredNullable.IsSpecified(), "SimpleRequiredNullable field should not be set") + assert.Falsef(t, obj.SimpleRequiredNullable.IsNull(), "SimpleRequiredNullable field should not be null") + + assert.Falsef(t, obj.SimpleOptionalNullable.IsSpecified(), "SimpleOptionalNullable field should not be set") + assert.Falsef(t, obj.SimpleOptionalNullable.IsNull(), "SimpleOptionalNullable field should not be null") + + assert.Falsef(t, obj.ComplexOptionalNullable.IsSpecified(), "ComplexOptionalNullable field should not be set") + assert.Falsef(t, obj.ComplexOptionalNullable.IsNull(), "ComplexOptionalNullable field should not be null") + + assert.Falsef(t, obj.ComplexRequiredNullable.IsSpecified(), "ComplexRequiredNullable field should not be set") + assert.Falsef(t, obj.ComplexRequiredNullable.IsNull(), "ComplexRequiredNullable field should not be null") + + // check for non-nullable field + assert.Nilf(t, obj.SimpleOptionalNonNullable, "SimpleOptionalNonNullable field should be nil") + }, + }, + + { + name: "when only empty complex_optional_nullable is provided", + json: []byte(`{"complex_optional_nullable":{}}`), + assert: func(t *testing.T, obj PatchRequest) { + t.Helper() + // check for nullable field + assert.Truef(t, obj.ComplexOptionalNullable.IsSpecified(), "ComplexOptionalNullable field should be set") + assert.Falsef(t, obj.ComplexOptionalNullable.IsNull(), "ComplexOptionalNullable field should not be null") + + // other simple nullable fields should not be set and should not be null + assert.Falsef(t, obj.SimpleRequiredNullable.IsSpecified(), "SimpleRequiredNullable field should not be set") + assert.Falsef(t, obj.SimpleRequiredNullable.IsNull(), "SimpleRequiredNullable field should not be null") + + assert.Falsef(t, obj.SimpleOptionalNullable.IsSpecified(), "SimpleOptionalNullable field should not be set") + assert.Falsef(t, obj.SimpleOptionalNullable.IsNull(), "SimpleOptionalNullable field should not be null") + + // other complex nullable fields should not be set and should not be null + assert.Falsef(t, obj.ComplexRequiredNullable.IsSpecified(), "ComplexRequiredNullable field should not be set") + assert.Falsef(t, obj.ComplexRequiredNullable.IsNull(), "ComplexRequiredNullable field should not be null") + + // other non-nullable field should have its zero value + assert.Nilf(t, obj.SimpleOptionalNonNullable, "SimpleOptionalNonNullable field should be nil") + + }, + }, + + { + name: "when only complex_optional_nullable with its `name` child field is provided", + json: []byte(`{"complex_optional_nullable":{"name":"test-name"}}`), + assert: func(t *testing.T, obj PatchRequest) { + t.Helper() + + assert.Truef(t, obj.ComplexOptionalNullable.IsSpecified(), "ComplexOptionalNullable field should be set") + assert.Falsef(t, obj.ComplexOptionalNullable.IsNull(), "ComplexOptionalNullable field should not be null") + + gotComplexObj, err := obj.ComplexOptionalNullable.Get() + require.NoError(t, err) + assert.Equalf(t, "test-name", string(*gotComplexObj.Name), "name should be test-name") + + assert.Falsef(t, gotComplexObj.AliasName.IsSpecified(), "child field `alias name` should not be specified") + assert.Falsef(t, gotComplexObj.AliasName.IsNull(), "child field `alias name` should not be null") + }, + }, + + { + name: "when only complex_optional_nullable child fields `name` and `alias name` are provided with non-zero and null values respectively", + json: []byte(`{"complex_optional_nullable":{"name":"test-name","alias_name":null}}`), + assert: func(t *testing.T, obj PatchRequest) { + t.Helper() + + assert.Truef(t, obj.ComplexOptionalNullable.IsSpecified(), "ComplexOptionalNullable field should be set") + assert.Falsef(t, obj.ComplexOptionalNullable.IsNull(), "ComplexOptionalNullable field should not be null") + + gotComplexObj, err := obj.ComplexOptionalNullable.Get() + require.NoError(t, err) + assert.Equalf(t, "test-name", string(*gotComplexObj.Name), "name should be test-name") + + assert.Truef(t, gotComplexObj.AliasName.IsSpecified(), "child field `alias name` should be set") + assert.Truef(t, gotComplexObj.AliasName.IsNull(), "child field `alias name` should be null") + }, + }, + + { + name: "when simple_required_nullable is null ", + json: []byte(`{"simple_required_nullable":null}`), + assert: func(t *testing.T, obj PatchRequest) { + t.Helper() + + assert.Truef(t, obj.SimpleRequiredNullable.IsSpecified(), "SimpleRequiredNullable field should be set") + assert.Truef(t, obj.SimpleRequiredNullable.IsNull(), "SimpleRequiredNullable field should be null") + }, + }, + + { + name: "when simple_required_nullable is null and organization has non zero value", + json: []byte(`{"complex_optional_nullable":{"name":"foo","alias_name":"bar"},"simple_required_nullable":null}`), + assert: func(t *testing.T, obj PatchRequest) { + t.Helper() + + assert.Truef(t, obj.SimpleRequiredNullable.IsSpecified(), "SimpleRequiredNullable field should be set") + assert.Truef(t, obj.SimpleRequiredNullable.IsNull(), "SimpleRequiredNullable field should be null") + + assert.Truef(t, obj.ComplexOptionalNullable.IsSpecified(), "ComplexOptionalNullable field should be set") + assert.Falsef(t, obj.ComplexOptionalNullable.IsNull(), "ComplexOptionalNullable field should not be null") + + gotComplexObj, err := obj.ComplexOptionalNullable.Get() + require.NoError(t, err) + assert.Equalf(t, "foo", string(*gotComplexObj.Name), "child field `name` should be foo") + + assert.Truef(t, gotComplexObj.AliasName.IsSpecified(), "child field `alias name` should be set") + assert.Falsef(t, gotComplexObj.AliasName.IsNull(), "child field `alias name` should not be null") + + gotAliasName, err := gotComplexObj.AliasName.Get() + require.NoError(t, err) + assert.Equalf(t, "bar", gotAliasName, "child field `alias name` should be bar") + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var obj PatchRequest + err := json.Unmarshal(tt.json, &obj) + require.NoError(t, err) + + tt.assert(t, obj) + }) + } +} diff --git a/internal/test/issues/issue-1039/server-config.yaml b/internal/test/issues/issue-1039/server-config.yaml new file mode 100644 index 0000000000..e1e7879932 --- /dev/null +++ b/internal/test/issues/issue-1039/server-config.yaml @@ -0,0 +1,5 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: issue1039 +generate: + chi-server: true +output: server.gen.go diff --git a/internal/test/issues/issue-1039/server.gen.go b/internal/test/issues/issue-1039/server.gen.go new file mode 100644 index 0000000000..a39f95dae7 --- /dev/null +++ b/internal/test/issues/issue-1039/server.gen.go @@ -0,0 +1,170 @@ +// Package issue1039 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue1039 + +import ( + "fmt" + "net/http" + + "github.com/go-chi/chi/v5" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (PATCH /example) + ExamplePatch(w http.ResponseWriter, r *http.Request) +} + +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct{} + +// (PATCH /example) +func (_ Unimplemented) ExamplePatch(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// ExamplePatch operation middleware +func (siw *ServerInterfaceWrapper) ExamplePatch(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ExamplePatch(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{}) +} + +type ChiServerOptions struct { + BaseURL string + BaseRouter chi.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = chi.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.Group(func(r chi.Router) { + r.Patch(options.BaseURL+"/example", wrapper.ExamplePatch) + }) + + return r +} diff --git a/internal/test/issues/issue-1039/spec.yaml b/internal/test/issues/issue-1039/spec.yaml new file mode 100644 index 0000000000..821b71066e --- /dev/null +++ b/internal/test/issues/issue-1039/spec.yaml @@ -0,0 +1,84 @@ +openapi: 3.0.1 +info: + version: '0.0.1' + title: example + description: | + Make sure that nullable types are generated properly +paths: + /example: + patch: + operationId: examplePatch + requestBody: + description: The patch body + required: true + content: + application/json: + example: + name: Example Patch + schema: + $ref: "#/components/schemas/PatchRequest" + responses: + '200': + description: "OK" + +components: + schemas: + PatchRequest: + type: object + description: A request to patch an existing user object. + required: + - simple_required_nullable + - complex_required_nullable + properties: + simple_required_nullable: + # required and nullable + $ref: "#/components/schemas/simple_required_nullable" + simple_optional_nullable: + # optional and nullable + $ref: "#/components/schemas/simple_optional_nullable" + simple_optional_non_nullable: + # optional and non-nullable + $ref: "#/components/schemas/simple_optional_non_nullable" + complex_required_nullable: + # required and nullable + $ref: "#/components/schemas/complex_required_nullable" + complex_optional_nullable: + # optional and nullable + $ref: "#/components/schemas/complex_optional_nullable" + additionalProperties: false + + simple_required_nullable: + type: integer + nullable: true + description: Simple required and nullable + + simple_optional_nullable: + type: integer + nullable: true + description: Simple optional and nullable + + simple_optional_non_nullable: + type: string + description: Simple optional and non nullable + + complex_required_nullable: + type: object + nullable: true + description: Complex required and nullable + properties: + name: + description: Optional and non nullable + type: string + + complex_optional_nullable: + type: object + description: Complex, optional and nullable + properties: + alias_name: + description: Optional and nullable + type: string + nullable: true + name: + description: Optional and non nullable + type: string + nullable: true diff --git a/internal/test/issues/issue-1039/type-config-defaultbehaviour.yaml b/internal/test/issues/issue-1039/type-config-defaultbehaviour.yaml new file mode 100644 index 0000000000..2b07679083 --- /dev/null +++ b/internal/test/issues/issue-1039/type-config-defaultbehaviour.yaml @@ -0,0 +1,5 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: defaultbehaviour +generate: + models: true +output: defaultbehaviour/types.gen.go diff --git a/internal/test/issues/issue-1039/types-config.yaml b/internal/test/issues/issue-1039/types-config.yaml new file mode 100644 index 0000000000..9f97d8bc62 --- /dev/null +++ b/internal/test/issues/issue-1039/types-config.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: issue1039 +generate: + models: true +output-options: + nullable-type: true +output: types.gen.go diff --git a/internal/test/issues/issue-1039/types.gen.go b/internal/test/issues/issue-1039/types.gen.go new file mode 100644 index 0000000000..9c11915344 --- /dev/null +++ b/internal/test/issues/issue-1039/types.gen.go @@ -0,0 +1,53 @@ +// Package issue1039 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue1039 + +import ( + "github.com/oapi-codegen/nullable" +) + +// PatchRequest A request to patch an existing user object. +type PatchRequest struct { + // ComplexOptionalNullable Complex, optional and nullable + ComplexOptionalNullable nullable.Nullable[ComplexOptionalNullable] `json:"complex_optional_nullable,omitempty"` + + // ComplexRequiredNullable Complex required and nullable + ComplexRequiredNullable nullable.Nullable[ComplexRequiredNullable] `json:"complex_required_nullable"` + + // SimpleOptionalNonNullable Simple optional and non nullable + SimpleOptionalNonNullable *SimpleOptionalNonNullable `json:"simple_optional_non_nullable,omitempty"` + + // SimpleOptionalNullable Simple optional and nullable + SimpleOptionalNullable nullable.Nullable[SimpleOptionalNullable] `json:"simple_optional_nullable,omitempty"` + + // SimpleRequiredNullable Simple required and nullable + SimpleRequiredNullable nullable.Nullable[SimpleRequiredNullable] `json:"simple_required_nullable"` +} + +// ComplexOptionalNullable Complex, optional and nullable +type ComplexOptionalNullable struct { + // AliasName Optional and nullable + AliasName nullable.Nullable[string] `json:"alias_name,omitempty"` + + // Name Optional and non nullable + Name *string `json:"name,omitempty"` +} + +// ComplexRequiredNullable Complex required and nullable +type ComplexRequiredNullable struct { + // Name Optional and non nullable + Name *string `json:"name,omitempty"` +} + +// SimpleOptionalNonNullable Simple optional and non nullable +type SimpleOptionalNonNullable = string + +// SimpleOptionalNullable Simple optional and nullable +type SimpleOptionalNullable = int + +// SimpleRequiredNullable Simple required and nullable +type SimpleRequiredNullable = int + +// ExamplePatchJSONRequestBody defines body for ExamplePatch for application/json ContentType. +type ExamplePatchJSONRequestBody = PatchRequest diff --git a/internal/test/issues/issue-1087/api.gen.go b/internal/test/issues/issue-1087/api.gen.go new file mode 100644 index 0000000000..3c408cc8f9 --- /dev/null +++ b/internal/test/issues/issue-1087/api.gen.go @@ -0,0 +1,452 @@ +// Package issue1087 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue1087 + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/go-chi/chi/v5" + externalRef0 "github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-1087/deps" +) + +// Thing defines model for Thing. +type Thing struct { + // Name just a name + Name string `json:"name"` +} + +// ThingList Object containing list of Things +type ThingList struct { + Keys []Thing `json:"keys"` +} + +// N404 defines model for 404. +type N404 = externalRef0.Error + +// ThingResponse Object containing list of Things +type ThingResponse = ThingList + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetThings request + GetThings(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) GetThings(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetThingsRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetThingsRequest generates requests for GetThings +func NewGetThingsRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/my/path") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetThingsWithResponse request + GetThingsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetThingsResponse, error) +} + +type GetThingsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *ThingResponse + JSON401 *externalRef0.N401 + JSON403 *externalRef0.N403 + JSON404 *N404 + JSON500 *externalRef0.DefaultError +} + +// Status returns HTTPResponse.Status +func (r GetThingsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetThingsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// GetThingsWithResponse request returning *GetThingsResponse +func (c *ClientWithResponses) GetThingsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetThingsResponse, error) { + rsp, err := c.GetThings(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetThingsResponse(rsp) +} + +// ParseGetThingsResponse parses an HTTP response from a GetThingsWithResponse call +func ParseGetThingsResponse(rsp *http.Response) (*GetThingsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetThingsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest ThingResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest externalRef0.N401 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest externalRef0.N403 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest N404 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest externalRef0.DefaultError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + case rsp.StatusCode == 401: + // Content-type (text/plain) unsupported + + case rsp.StatusCode == 403: + // Content-type (text/plain) unsupported + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // list things + // (GET /api/my/path) + GetThings(w http.ResponseWriter, r *http.Request) +} + +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct{} + +// list things +// (GET /api/my/path) +func (_ Unimplemented) GetThings(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetThings operation middleware +func (siw *ServerInterfaceWrapper) GetThings(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetThings(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{}) +} + +type ChiServerOptions struct { + BaseURL string + BaseRouter chi.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = chi.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/api/my/path", wrapper.GetThings) + }) + + return r +} diff --git a/internal/test/issues/issue-1087/deps/config.yaml b/internal/test/issues/issue-1087/deps/config.yaml new file mode 100644 index 0000000000..95c23f63e0 --- /dev/null +++ b/internal/test/issues/issue-1087/deps/config.yaml @@ -0,0 +1,9 @@ +# yaml-language-server: $schema=../../../../../configuration-schema.json +package: deps +output: + deps.gen.go +output-options: + skip-prune: true +generate: + models: true + embedded-spec: true diff --git a/internal/test/issues/issue-1087/deps/deps.gen.go b/internal/test/issues/issue-1087/deps/deps.gen.go new file mode 100644 index 0000000000..8066ab2b57 --- /dev/null +++ b/internal/test/issues/issue-1087/deps/deps.gen.go @@ -0,0 +1,138 @@ +// Package deps provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package deps + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" +) + +// BaseError defines model for BaseError. +type BaseError struct { + // Code The underlying http status code + Code int32 `json:"code"` + + // Domain The domain where the error is originating from as defined by the service + Domain string `json:"domain"` + + // Message A simple message in english describing the error and can be returned to the consumer + Message string `json:"message"` + + // Metadata Any additional details to be conveyed as determined by the service. If present, will return map of key value pairs + Metadata *map[string]string `json:"metadata,omitempty"` +} + +// Error defines model for Error. +type Error = BaseError + +// N401 defines model for 401. +type N401 = Error + +// N403 defines model for 403. +type N403 = Error + +// N410 defines model for 410. +type N410 = Error + +// DefaultError defines model for DefaultError. +type DefaultError = Error + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/7SV34/jNBDH/5WR4TFq0+siobwVDtAiAacDnk6rlRtPmjmcsc+edLes+r8jO2nTH6tb", + "7qR9ahPPeL7zmR95UrXrvGNkiap6UgGjdxwxP9yUi/RTOxZkSX+195ZqLeR4/jE6Tu/wUXfe4mBpUFU3", + "5aJQxnWaWFVq1UurCtVhjHqDqlK3vNWWDOheWmQZr1OFCqjzlQeL+9W5xb5QsW6x0ynUtwEbValv5pP+", + "+XAa5z+F4ILa7wsl+Chzb7OSpxPvo2b164MARcBHTwGNKpTsfHofJRBv1D7dYjDWgXwWUam/OSl3gf5F", + "M4NbNkkfRpBWC/jgtmTQQB3QJO3axnQ/O4GcVMriplx+Hdfl57iu6hpjhLfIlBM54hwO7seDV6F4GftF", + "iD+7sCZjkK8IBvzUYxSoNSdoa4QJd4a3KL8K3qI8gXcG7hfHeMorP78KpjHSi3TeY+2CAXZgHW8wgN5q", + "snpts6632OjeyhD4ZRRflsaVljEaYDKAw36AxgXQvBteRyAGaRFW724zivHWFPQHHfEo1QfnMQgN+2Wo", + "zNNFwL9ahJ4NBrsj3kAr4iGKlj5CdigmoN+VZaEaFzotqlLEsnwz4SUW3GBIxA51fy7UcAYPLQbMOQyJ", + "UgQXaEOsJalogutARzDYEKOB9S7bRgxbqs80qesCnzTbpYIVREp+MFokkMgbS7GFwXKdwk+6NJs0Gmku", + "Akofkhhx2aB2HPsOw5ma1QZPRsmmKZVWMyy+f16naKMld4s2hpJKbd+dVe3K6SIj3sHkCgZFk41J4zpL", + "3OIOzYBSMHTP0JzBbQM+YESWAh7I2jFV6LQH18A/uEvLtEfwmkI8zffYYrvfdZdknj6moqb1kjf9/pi+", + "W3/EWnLfHk+rD2pstrF3phreXTkW6tjg2to/GlV9+PywTTOxvysuhuKwhq47ZTjJQwDRY00N1Yfaj+hO", + "26OPQ2tQ/g41A2J81HX64MUeZ/Bn63prsi3Tpx7hgaQlBg3HpKdGep+j3/84ULlcYf8P3XHJXjNMVxA3", + "LncYSQ75mzNoU3m3GOJA4c2snJWJuPPI2pOq1HJWzpaqUF5Lmwim9YMhueQ69MGqSs3V/m7/XwAAAP//", + "TAbhS+0IAAA=", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/issues/issue-1087/deps/doc.go b/internal/test/issues/issue-1087/deps/doc.go new file mode 100644 index 0000000000..0684d56cae --- /dev/null +++ b/internal/test/issues/issue-1087/deps/doc.go @@ -0,0 +1,3 @@ +package deps + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml my-deps.json diff --git a/internal/test/issues/issue-1087/deps/my-deps.json b/internal/test/issues/issue-1087/deps/my-deps.json new file mode 100644 index 0000000000..57674e1212 --- /dev/null +++ b/internal/test/issues/issue-1087/deps/my-deps.json @@ -0,0 +1,153 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Models", + "description": "", + "version": "2.0.0" + }, + "servers": [ + { + "url": "/" + } + ], + "paths": {}, + "components": { + "responses": { + "DefaultError": { + "description": "Default error response for any errors in the API", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden. Indicates that request cannot be authorized", + "content": { + "text/plain": { + "schema": { + "type": "string", + "example": "Access Denied" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "code": 403, + "domain": "Auth", + "message": "Access Denied", + "reason": "Access_Denied" + } + } + } + }, + "401": { + "description": "Unauthorized. Indicates that provided credentials is not valid", + "content": { + "text/plain": { + "schema": { + "type": "string", + "example": "Jwt is expired" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "code": 401, + "domain": "Auth", + "message": "Invalid authentication", + "reason": "Invalid_Authentication" + } + } + } + }, + "410": { + "description": "Record no longer available", + "content": { + "text/plain": { + "schema": { + "type": "string", + "example": "Gone" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "code": 410, + "domain": "", + "message": "Gone", + "reason": "Gone" + } + } + } + } + }, + "schemas": { + "Error": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseError" + } + ], + "required": [ + "code", + "domain", + "message", + "reason" + ], + "type": "object", + "properties": { + "reason": { + "type": "string", + "description": "A reason code specific to the service and can be used to identify the exact issue. Should be unique within a domain", + "example": "Reason_Code" + } + } + }, + "BaseError": { + "required": [ + "code", + "domain", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "description": "The underlying http status code", + "format": "int32", + "example": 500 + }, + "message": { + "type": "string", + "description": "A simple message in english describing the error and can be returned to the consumer", + "example": "Age cannot be less than 18" + }, + "domain": { + "type": "string", + "description": "The domain where the error is originating from as defined by the service", + "example": "" + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Any additional details to be conveyed as determined by the service. If present, will return map of key value pairs", + "example": { + "propertyName": "propertyName is required" + } + } + } + } + } + } +} diff --git a/internal/test/issues/issue-1087/doc.go b/internal/test/issues/issue-1087/doc.go new file mode 100644 index 0000000000..2ad8db520c --- /dev/null +++ b/internal/test/issues/issue-1087/doc.go @@ -0,0 +1,3 @@ +package issue1087 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=server.config.yaml spec.yaml diff --git a/internal/test/issues/issue-1087/server.config.yaml b/internal/test/issues/issue-1087/server.config.yaml new file mode 100644 index 0000000000..6d795c05d7 --- /dev/null +++ b/internal/test/issues/issue-1087/server.config.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: issue1087 +output: + api.gen.go +generate: + chi-server: true + models: true + embedded-spec: false + client: true +import-mapping: + ./deps/my-deps.json: github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-1087/deps diff --git a/internal/test/issues/issue-1087/spec.yaml b/internal/test/issues/issue-1087/spec.yaml new file mode 100644 index 0000000000..5686be9b56 --- /dev/null +++ b/internal/test/issues/issue-1087/spec.yaml @@ -0,0 +1,109 @@ +openapi: 3.0.3 +info: + title: test + description: "The API for Test" + version: 2.0.0 +servers: + - url: http://localhost:8000 +tags: + - name: Tag + description: Foo Bar +paths: + /api/my/path: + get: + tags: + - Tag + summary: list things + description: my list of things + operationId: getThings + responses: + 200: + $ref: "#/components/responses/ThingResponse" + 304: + $ref: "#/components/responses/304" + 401: + $ref: "./deps/my-deps.json#/components/responses/401" + 403: + $ref: "./deps/my-deps.json#/components/responses/403" + 404: + $ref: "#/components/responses/404" + 500: + $ref: "./deps/my-deps.json#/components/responses/DefaultError" +components: + securitySchemes: + bearerAuthWebhook: + type: http + scheme: bearer + bearerFormat: JWT + description: The JWT access token. + responses: + Empty: + description: No content + ThingResponse: + description: List of Things + content: + application/json: + schema: + $ref: "#/components/schemas/ThingList" + 304: + description: Response when client should use cached results + headers: + Cache-Control: + description: defines TTL of resource in seconds, indicates that the response can be stored in caches, and indicates that the response can be stored only in a private cache + example: "max-age: 86400, must-revalidate, private" + schema: + type: string + ETag: + description: "indicate the current version of the resource" + schema: + type: string + 404: + description: Not Found. Resource could not be found + content: + application/json: + schema: + $ref: "./deps/my-deps.json#/components/schemas/Error" + example: + code: 404 + domain: "Foo" + message: "Not Found. Subscription could not be found" + reason: "Not_Found" + 409: + description: Conflict. Resource already exists + content: + application/json: + schema: + $ref: "./deps/my-deps.json#/components/schemas/Error" + example: + code: 404 + domain: "Webhook" + message: "Conflict. Resource already exists" + reason: "Conflict" + 410: + description: Gone. Record no longer available + content: + application/json: + schema: + $ref: "./deps/my-deps.json#/components/schemas/Error" + example: + code: 403 + domain: "" + message: "Gone" + reason: "Already deleted" + schemas: + Thing: + type: object + properties: + name: + type: string + description: just a name + required: [ name] + ThingList: + type: object + description: Object containing list of Things + properties: + keys: + type: array + items: + $ref: "#/components/schemas/Thing" + required: [ keys ] diff --git a/internal/test/issues/issue-1093/api/child/child.gen.go b/internal/test/issues/issue-1093/api/child/child.gen.go new file mode 100644 index 0000000000..203fad36e8 --- /dev/null +++ b/internal/test/issues/issue-1093/api/child/child.gen.go @@ -0,0 +1,230 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/gin-gonic/gin" + externalRef0 "github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-1093/api/parent" + strictgin "github.com/oapi-codegen/runtime/strictmiddleware/gin" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /pets) + GetPets(c *gin.Context) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) +} + +type MiddlewareFunc func(c *gin.Context) + +// GetPets operation middleware +func (siw *ServerInterfaceWrapper) GetPets(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.GetPets(c) +} + +// GinServerOptions provides options for the Gin server. +type GinServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router gin.IRouter, si ServerInterface) { + RegisterHandlersWithOptions(router, si, GinServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options GinServerOptions) { + errorHandler := options.ErrorHandler + if errorHandler == nil { + errorHandler = func(c *gin.Context, err error, statusCode int) { + c.JSON(statusCode, gin.H{"msg": err.Error()}) + } + } + + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandler: errorHandler, + } + + router.GET(options.BaseURL+"/pets", wrapper.GetPets) +} + +type GetPetsRequestObject struct { +} + +type GetPetsResponseObject interface { + VisitGetPetsResponse(w http.ResponseWriter) error +} + +type GetPets200JSONResponse externalRef0.Pet + +func (response GetPets200JSONResponse) VisitGetPetsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (GET /pets) + GetPets(ctx context.Context, request GetPetsRequestObject) (GetPetsResponseObject, error) +} + +type StrictHandlerFunc = strictgin.StrictGinHandlerFunc +type StrictMiddlewareFunc = strictgin.StrictGinMiddlewareFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +// GetPets operation middleware +func (sh *strictHandler) GetPets(ctx *gin.Context) { + var request GetPetsRequestObject + + handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { + return sh.ssi.GetPets(ctx, request.(GetPetsRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetPets") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.Error(err) + ctx.Status(http.StatusInternalServerError) + } else if validResponse, ok := response.(GetPetsResponseObject); ok { + if err := validResponse.VisitGetPetsResponse(ctx.Writer); err != nil { + ctx.Error(err) + } + } else if response != nil { + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) + } +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/2xRwW7bMAz9FYHb0Yi97aYfGHYLht7SoFBlOlZgSyxJFwgC/3tBuUEaoCeR9uPj43tX", + "iGWmkjGrgL+CxBHnUEsKjFlf9qi140LImrD+y2FGe/VCCB5EOeUTrA1oOH3zfW2A8W1JjD34wzZ9bG6o", + "8nrGqLAaLOWhGEGPEjmRppLBw9OYxMUxTb0TwujSTIVV3Fx6nMQNXGanI7pNcsVAA5p0Mv46CA28I8vG", + "92vXmdhCmAMl8PBn1+06aICCjvXAlnAz5LSd/6jnP+rCWRyh3pfLRRStDFr7RZDdGMSFGFHEaXnOUJdy", + "MJ5/PXj4i7q3TWaQUMmy+fu76+yJJSvmKiAQTSnWwfYspuIWllU/GQfw8KO9p9l+Rtl+ybFa/HhKcLJU", + "fcMyuZsGAxpUkM008IcrLDyBh3Yzcz2uHwEAAP//SKTQrDoCAAA=", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + for rawPath, rawFunc := range externalRef0.PathToRawSpec(path.Join(path.Dir(pathToFile), "parent.api.yaml")) { + if _, ok := res[rawPath]; ok { + // it is not possible to compare functions in golang, so always overwrite the old value + } + res[rawPath] = rawFunc + } + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/issues/issue-1093/api/parent/parent.gen.go b/internal/test/issues/issue-1093/api/parent/parent.gen.go new file mode 100644 index 0000000000..916772b07d --- /dev/null +++ b/internal/test/issues/issue-1093/api/parent/parent.gen.go @@ -0,0 +1,229 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/gin-gonic/gin" + strictgin "github.com/oapi-codegen/runtime/strictmiddleware/gin" +) + +// Pet defines model for Pet. +type Pet struct { + Name string `json:"name"` + Tag *string `json:"tag,omitempty"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /pets) + GetPets(c *gin.Context) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) +} + +type MiddlewareFunc func(c *gin.Context) + +// GetPets operation middleware +func (siw *ServerInterfaceWrapper) GetPets(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.GetPets(c) +} + +// GinServerOptions provides options for the Gin server. +type GinServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router gin.IRouter, si ServerInterface) { + RegisterHandlersWithOptions(router, si, GinServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options GinServerOptions) { + errorHandler := options.ErrorHandler + if errorHandler == nil { + errorHandler = func(c *gin.Context, err error, statusCode int) { + c.JSON(statusCode, gin.H{"msg": err.Error()}) + } + } + + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandler: errorHandler, + } + + router.GET(options.BaseURL+"/pets", wrapper.GetPets) +} + +type GetPetsRequestObject struct { +} + +type GetPetsResponseObject interface { + VisitGetPetsResponse(w http.ResponseWriter) error +} + +type GetPets200JSONResponse Pet + +func (response GetPets200JSONResponse) VisitGetPetsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (GET /pets) + GetPets(ctx context.Context, request GetPetsRequestObject) (GetPetsResponseObject, error) +} + +type StrictHandlerFunc = strictgin.StrictGinHandlerFunc +type StrictMiddlewareFunc = strictgin.StrictGinMiddlewareFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +// GetPets operation middleware +func (sh *strictHandler) GetPets(ctx *gin.Context) { + var request GetPetsRequestObject + + handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { + return sh.ssi.GetPets(ctx, request.(GetPetsRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetPets") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.Error(err) + ctx.Status(http.StatusInternalServerError) + } else if validResponse, ok := response.(GetPetsResponseObject); ok { + if err := validResponse.VisitGetPetsResponse(ctx.Writer); err != nil { + ctx.Error(err) + } + } else if response != nil { + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) + } +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/2xRwa7TMBD8FWvhGDUBbv4BxO0JcXv04Dqb2lViL7sbUBXl39E6rVAlTru2xjPjmQ1i", + "XagWLCrgN5CYcAltfUO1QVwJWTO2yxIWtKl3QvAgyrlcYe9Aw/U/93sHjL/WzDiCfz9en7snql5uGBV2", + "g+UyVSMYUSJn0lwLePiRsjgKjEWdEEaXgriljjiL+5NyTC4wurxQZcXRXe5OE7qY8jw2PHSgWWcTO1ig", + "g9/IcrB/Og1mvRKWQBk8fDkNpwE6oKCpfbcnPHK5HmG8uvuOunIRR6hu4ro0cbmLoq1B23kV5GY7xIgi", + "TuvPAk2Ug/F8G8HDV9Q3U7K4hGqRI+3Pw2Aj1qLm3W8QiOYc28P+Jubi2ZltHxkn8PCh/1dq/2i0tzpb", + "0q9/CE7WZmxaZ/cUN6BBBdnSAv++wcozeOgfMe7n/W8AAAD///mNvoM7AgAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/issues/issue-1093/child.api.yaml b/internal/test/issues/issue-1093/child.api.yaml new file mode 100644 index 0000000000..c373c1ec2a --- /dev/null +++ b/internal/test/issues/issue-1093/child.api.yaml @@ -0,0 +1,21 @@ +# child.api.yaml +openapi: 3.0.0 +info: + description: >- + This child spec imports models from the parent spec + title: child + version: "1.0" +servers: + - url: /child +paths: + /pets: + get: + description: | + Returns pet from the system that the user has access to + responses: + '200': + description: a successful response + content: + application/json: + schema: + $ref: "parent.api.yaml#/components/schemas/Pet" \ No newline at end of file diff --git a/internal/test/issues/issue-1093/child.cfg.yaml b/internal/test/issues/issue-1093/child.cfg.yaml new file mode 100644 index 0000000000..4efb750c1e --- /dev/null +++ b/internal/test/issues/issue-1093/child.cfg.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +# child.yaml +package: api +generate: + gin-server: true + embedded-spec: true + strict-server: true + models: true +import-mapping: + parent.api.yaml: github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-1093/api/parent +output: api/child/child.gen.go diff --git a/internal/test/issues/issue-1093/doc.go b/internal/test/issues/issue-1093/doc.go new file mode 100644 index 0000000000..6996cdd2d8 --- /dev/null +++ b/internal/test/issues/issue-1093/doc.go @@ -0,0 +1,6 @@ +// This is an example of how to reference models of one api specification from another. +// See https://github.com/oapi-codegen/oapi-codegen/issues/1093 +package issue1093 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config parent.cfg.yaml parent.api.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config child.cfg.yaml child.api.yaml diff --git a/internal/test/issues/issue-1093/issue_test.go b/internal/test/issues/issue-1093/issue_test.go new file mode 100644 index 0000000000..990de85384 --- /dev/null +++ b/internal/test/issues/issue-1093/issue_test.go @@ -0,0 +1,38 @@ +package issue1093 + +import ( + _ "embed" + "testing" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/stretchr/testify/require" + + "github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen" +) + +//go:embed child.api.yaml +var spec []byte + +func TestIssue(t *testing.T) { + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + + swagger, err := loader.LoadFromData(spec) + require.NoError(t, err) + + opts := codegen.Configuration{ + PackageName: "issue1093", + Generate: codegen.GenerateOptions{ + GinServer: true, + Strict: true, + Models: true, + EmbeddedSpec: true, + }, + ImportMapping: map[string]string{ + "parent.api.yaml": "github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-1093/api/parent", + }, + } + + _, err = codegen.Generate(swagger, opts) + require.NoError(t, err) +} diff --git a/internal/test/issues/issue-1093/parent.api.yaml b/internal/test/issues/issue-1093/parent.api.yaml new file mode 100644 index 0000000000..95687168a9 --- /dev/null +++ b/internal/test/issues/issue-1093/parent.api.yaml @@ -0,0 +1,32 @@ +# parent.api.yaml +openapi: 3.0.0 +info: + description: >- + This parent spec has models which are imported by the child spec + title: parent + version: "1.0" +servers: + - url: /parent +paths: + /pets: + get: + description: | + Returns pet from the system that the user has access to + responses: + '200': + description: a successful response + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" +components: + schemas: + Pet: + type: object + required: + - name + properties: + name: + type: string + tag: + type: string \ No newline at end of file diff --git a/internal/test/issues/issue-1093/parent.cfg.yaml b/internal/test/issues/issue-1093/parent.cfg.yaml new file mode 100644 index 0000000000..ef00a99d61 --- /dev/null +++ b/internal/test/issues/issue-1093/parent.cfg.yaml @@ -0,0 +1,10 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +generate: + gin-server: true + embedded-spec: true + strict-server: true + models: true +output: api/parent/parent.gen.go +output-options: + skip-prune: true diff --git a/internal/test/issues/issue-1127/api.gen.go b/internal/test/issues/issue-1127/api.gen.go new file mode 100644 index 0000000000..d3fa8e4293 --- /dev/null +++ b/internal/test/issues/issue-1127/api.gen.go @@ -0,0 +1,18 @@ +// Package issue1127 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue1127 + +// Whatever defines model for Whatever. +type Whatever struct { + SomeProperty *string `json:"someProperty,omitempty"` +} + +// CreateWhateverApplicationWildcardPlusJSONRequestBody defines body for CreateWhatever for application/*+json ContentType. +type CreateWhateverApplicationWildcardPlusJSONRequestBody = Whatever + +// CreateWhateverJSONRequestBody defines body for CreateWhatever for application/json ContentType. +type CreateWhateverJSONRequestBody = Whatever + +// CreateWhateverApplicationJSONPatchPlusJSONRequestBody defines body for CreateWhatever for application/json-patch+json ContentType. +type CreateWhateverApplicationJSONPatchPlusJSONRequestBody = Whatever diff --git a/internal/test/issues/issue-1127/doc.go b/internal/test/issues/issue-1127/doc.go new file mode 100644 index 0000000000..d591e52f84 --- /dev/null +++ b/internal/test/issues/issue-1127/doc.go @@ -0,0 +1,3 @@ +package issue1127 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=server.config.yaml spec.yaml diff --git a/internal/test/issues/issue-1127/server.config.yaml b/internal/test/issues/issue-1127/server.config.yaml new file mode 100644 index 0000000000..fd6b9de19d --- /dev/null +++ b/internal/test/issues/issue-1127/server.config.yaml @@ -0,0 +1,6 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: issue1127 +output: + api.gen.go +generate: + models: true diff --git a/internal/test/issues/issue-1127/spec.yaml b/internal/test/issues/issue-1127/spec.yaml new file mode 100644 index 0000000000..e3eee06627 --- /dev/null +++ b/internal/test/issues/issue-1127/spec.yaml @@ -0,0 +1,42 @@ +openapi: 3.0.1 +info: + title: api + version: "0.1.0" +paths: + /whatever: + post: + operationId: CreateWhatever + requestBody: + content: + application/json-patch+json: + schema: + $ref: "#/components/schemas/Whatever" + application/json: + schema: + $ref: "#/components/schemas/Whatever" + text/json: + schema: + $ref: "#/components/schemas/Whatever" + application/*+json: + schema: + $ref: "#/components/schemas/Whatever" + responses: + 201: + description: Whatever created + content: + text/plain: + schema: + $ref: "#/components/schemas/Whatever" + application/json: + schema: + $ref: "#/components/schemas/Whatever" + text/json: + schema: + $ref: "#/components/schemas/Whatever" +components: + schemas: + Whatever: + type: object + properties: + someProperty: + type: string diff --git a/internal/test/issues/issue-1168/api.gen.go b/internal/test/issues/issue-1168/api.gen.go new file mode 100644 index 0000000000..d681fbd81c --- /dev/null +++ b/internal/test/issues/issue-1168/api.gen.go @@ -0,0 +1,162 @@ +// Package issue1168 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue1168 + +import ( + "encoding/json" + "fmt" +) + +// ProblemDetails defines model for ProblemDetails. +type ProblemDetails struct { + // Detail A human readable explanation specific to this occurrence of the problem. + Detail *string `json:"detail,omitempty"` + + // Instance An absolute URI that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced. + Instance *string `json:"instance,omitempty"` + + // Status The HTTP status code generated by the origin server for this occurrence of the problem. + Status *int32 `json:"status,omitempty"` + + // Title A short, summary of the problem type. Written in english and readable for engineers (usually not suited for non technical stakeholders and not localized); example: Service Unavailable + Title *string `json:"title,omitempty"` + + // Type An absolute URI that identifies the problem type. When dereferenced, it SHOULD provide human-readable documentation for the problem type (e.g., using HTML). + Type *string `json:"type,omitempty"` + AdditionalProperties map[string]interface{} `json:"-"` +} + +// Misc400Error defines model for Misc400Error. +type Misc400Error = ProblemDetails + +// Misc404Error defines model for Misc404Error. +type Misc404Error = ProblemDetails + +// Getter for additional properties for ProblemDetails. Returns the specified +// element and whether it was found +func (a ProblemDetails) Get(fieldName string) (value interface{}, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for ProblemDetails +func (a *ProblemDetails) Set(fieldName string, value interface{}) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]interface{}) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for ProblemDetails to handle AdditionalProperties +func (a *ProblemDetails) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["detail"]; found { + err = json.Unmarshal(raw, &a.Detail) + if err != nil { + return fmt.Errorf("error reading 'detail': %w", err) + } + delete(object, "detail") + } + + if raw, found := object["instance"]; found { + err = json.Unmarshal(raw, &a.Instance) + if err != nil { + return fmt.Errorf("error reading 'instance': %w", err) + } + delete(object, "instance") + } + + if raw, found := object["status"]; found { + err = json.Unmarshal(raw, &a.Status) + if err != nil { + return fmt.Errorf("error reading 'status': %w", err) + } + delete(object, "status") + } + + if raw, found := object["title"]; found { + err = json.Unmarshal(raw, &a.Title) + if err != nil { + return fmt.Errorf("error reading 'title': %w", err) + } + delete(object, "title") + } + + if raw, found := object["type"]; found { + err = json.Unmarshal(raw, &a.Type) + if err != nil { + return fmt.Errorf("error reading 'type': %w", err) + } + delete(object, "type") + } + + if len(object) != 0 { + a.AdditionalProperties = make(map[string]interface{}) + for fieldName, fieldBuf := range object { + var fieldVal interface{} + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for ProblemDetails to handle AdditionalProperties +func (a ProblemDetails) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + if a.Detail != nil { + object["detail"], err = json.Marshal(a.Detail) + if err != nil { + return nil, fmt.Errorf("error marshaling 'detail': %w", err) + } + } + + if a.Instance != nil { + object["instance"], err = json.Marshal(a.Instance) + if err != nil { + return nil, fmt.Errorf("error marshaling 'instance': %w", err) + } + } + + if a.Status != nil { + object["status"], err = json.Marshal(a.Status) + if err != nil { + return nil, fmt.Errorf("error marshaling 'status': %w", err) + } + } + + if a.Title != nil { + object["title"], err = json.Marshal(a.Title) + if err != nil { + return nil, fmt.Errorf("error marshaling 'title': %w", err) + } + } + + if a.Type != nil { + object["type"], err = json.Marshal(a.Type) + if err != nil { + return nil, fmt.Errorf("error marshaling 'type': %w", err) + } + } + + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) +} diff --git a/internal/test/issues/issue-1168/doc.go b/internal/test/issues/issue-1168/doc.go new file mode 100644 index 0000000000..2541e721d1 --- /dev/null +++ b/internal/test/issues/issue-1168/doc.go @@ -0,0 +1,3 @@ +package issue1168 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=server.config.yaml spec.yaml diff --git a/internal/test/issues/issue-1168/server.config.yaml b/internal/test/issues/issue-1168/server.config.yaml new file mode 100644 index 0000000000..ff204a7c23 --- /dev/null +++ b/internal/test/issues/issue-1168/server.config.yaml @@ -0,0 +1,6 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: issue1168 +output: + api.gen.go +generate: + models: true diff --git a/internal/test/issues/issue-1168/spec.yaml b/internal/test/issues/issue-1168/spec.yaml new file mode 100644 index 0000000000..72af583253 --- /dev/null +++ b/internal/test/issues/issue-1168/spec.yaml @@ -0,0 +1,95 @@ +--- +# Copyright 2023 Coros, Corp. All Rights Reserved. +# +# Apache 2.0 Licensed; the portion included from +# https://github.com/zalando/problem/ is MIT licensed +# +openapi: 3.0.3 +info: + version: 1.0.0 + title: Test + description: Provides access to things + license: + name: Apache License 2.0 + url: http://www.example.com/XXX + +servers: + - url: https://www.example.com/api + description: Production environment + +paths: + /v1/test: + get: + summary: Get it + responses: + '200': + description: Successful response + content: + text/plain: + type: string + '400': + $ref: '#/components/responses/Misc400Error' + '404': + $ref: '#/components/responses/Misc404Error' + +components: + responses: + Misc400Error: + description: Bad request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + Misc404Error: + description: Not found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + + schemas: + # Fetched from https://opensource.zalando.com/problem/schema.yaml + # and slightly modified. + # Part of https://github.com/zalando/problem/; MIT License + ProblemDetails: + type: object + additionalProperties: true + properties: + type: + type: string + format: uri + description: >- + An absolute URI that identifies the problem type. When dereferenced, + it SHOULD provide human-readable documentation for the problem type + (e.g., using HTML). + default: 'about:blank' + example: 'https://zalando.github.io/problem/constraint-violation' + title: + type: string + description: >- + A short, summary of the problem type. Written in english and readable + for engineers (usually not suited for non technical stakeholders and + not localized); example: Service Unavailable + status: + type: integer + format: int32 + description: >- + The HTTP status code generated by the origin server for this occurrence + of the problem. + minimum: 100 + maximum: 600 + exclusiveMaximum: true + example: 503 + detail: + type: string + description: >- + A human readable explanation specific to this occurrence of the + problem. + example: Connection to database timed out + instance: + type: string + format: uri + description: >- + An absolute URI that identifies the specific occurrence of the problem. + It may or may not yield further information if dereferenced. + # diff --git a/internal/test/issues/issue-1180/config.yaml b/internal/test/issues/issue-1180/config.yaml new file mode 100644 index 0000000000..f8f01372f9 --- /dev/null +++ b/internal/test/issues/issue-1180/config.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: issue1180 +generate: + echo-server: true + client: true + models: true + embedded-spec: true +output: issue.gen.go diff --git a/internal/test/issues/issue-1180/doc.go b/internal/test/issues/issue-1180/doc.go new file mode 100644 index 0000000000..5fcca2b965 --- /dev/null +++ b/internal/test/issues/issue-1180/doc.go @@ -0,0 +1,3 @@ +package issue1180 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml issue.yaml diff --git a/internal/test/issues/issue-1180/issue.gen.go b/internal/test/issues/issue-1180/issue.gen.go new file mode 100644 index 0000000000..26e358477a --- /dev/null +++ b/internal/test/issues/issue-1180/issue.gen.go @@ -0,0 +1,380 @@ +// Package issue1180 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue1180 + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/labstack/echo/v4" + "github.com/oapi-codegen/runtime" +) + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetSimplePrimitive request + GetSimplePrimitive(ctx context.Context, param string, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) GetSimplePrimitive(ctx context.Context, param string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetSimplePrimitiveRequest(c.Server, param) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetSimplePrimitiveRequest generates requests for GetSimplePrimitive +func NewGetSimplePrimitiveRequest(server string, param string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "param", param, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: "int32"}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/simplePrimitive/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetSimplePrimitiveWithResponse request + GetSimplePrimitiveWithResponse(ctx context.Context, param string, reqEditors ...RequestEditorFn) (*GetSimplePrimitiveResponse, error) +} + +type GetSimplePrimitiveResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r GetSimplePrimitiveResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetSimplePrimitiveResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// GetSimplePrimitiveWithResponse request returning *GetSimplePrimitiveResponse +func (c *ClientWithResponses) GetSimplePrimitiveWithResponse(ctx context.Context, param string, reqEditors ...RequestEditorFn) (*GetSimplePrimitiveResponse, error) { + rsp, err := c.GetSimplePrimitive(ctx, param, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetSimplePrimitiveResponse(rsp) +} + +// ParseGetSimplePrimitiveResponse parses an HTTP response from a GetSimplePrimitiveWithResponse call +func ParseGetSimplePrimitiveResponse(rsp *http.Response) (*GetSimplePrimitiveResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetSimplePrimitiveResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /simplePrimitive/{param}) + GetSimplePrimitive(ctx echo.Context, param string) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// GetSimplePrimitive converts echo context to params. +func (w *ServerInterfaceWrapper) GetSimplePrimitive(ctx echo.Context) error { + var err error + // ------------- Path parameter "param" ------------- + var param string + + err = runtime.BindStyledParameterWithOptions("simple", "param", ctx.Param("param"), ¶m, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: "int32"}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter param: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetSimplePrimitive(ctx, param) + return err +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.GET(baseURL+"/simplePrimitive/:param", wrapper.GetSimplePrimitive) + +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/7RRPY/TQBD9K9aDcuX13XXbUaErkBC5DlEs9iQZ5P1gdhJxsvzf0a4TIBHtVfbMzrz3", + "5r0FYwo5RYpa4BYIlZxioVbsOOSZvlxatTOmqBS1/ir9Uptnz7FWZTxS8K3/mgkORYXjAeu6GkxURuGs", + "nCIcPnSl4XZXri59/0GjbrMc96nCzDzShTT6UBE/Pb9gNVDWuZYvVLTbkZxJYHAmKRv8Qz/0Qx1MmaLP", + "DIenfugfYJC9HtthdlPwWTiw8pnskr34sNa3A7XzUibxVfHzBIePpLvblQYnPpCSFLivC6oRjQLmKrlN", + "wEDo54mFJjiVE5l/3NonCV7hwFGfHmHu7TMo+tru3SRj/WZuQ3ochvp5L7SHwzv7N0/7Z87eJdmcfkv5", + "HJUOJP/VX7lLy20jPskMh6NqdtZeQlMq2k9EOfjce65bvwMAAP//QUrtxqoCAAA=", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/issues/issue-1180/issue.yaml b/internal/test/issues/issue-1180/issue.yaml new file mode 100644 index 0000000000..540996086e --- /dev/null +++ b/internal/test/issues/issue-1180/issue.yaml @@ -0,0 +1,39 @@ +openapi: "3.0.1" +info: + version: 1.0.0 + title: Test Server + license: + name: MIT +servers: + - url: http://openapitest.deepmap.ai +paths: + /simplePrimitive/{param}: + parameters: + - name: param + in: path + required: true + style: simple + schema: + type: integer + format: int32 + get: + operationId: getSimplePrimitive + parameters: + - name: param + in: path + required: true + style: simple + schema: + type: string + format: int32 + responses: + '200': + $ref: "#/components/responses/SimpleResponse" +components: + responses: + SimpleResponse: + description: A simple response object + content: + text/plain: + schema: + type: string diff --git a/internal/test/issues/issue-1180/issue_test.go b/internal/test/issues/issue-1180/issue_test.go new file mode 100644 index 0000000000..096e41d566 --- /dev/null +++ b/internal/test/issues/issue-1180/issue_test.go @@ -0,0 +1,10 @@ +package issue1180 + +import ( + "testing" +) + +// TestIssue1180 validates that the parameter `param` is a string type, rather than an int, as we should prioritise the `param` definition closest to the path +func TestIssue1180(t *testing.T) { + _, _ = NewGetSimplePrimitiveRequest("http://example.com/", "test-string") +} diff --git a/internal/test/issues/issue-1182/pkg1.yaml b/internal/test/issues/issue-1182/pkg1.yaml new file mode 100644 index 0000000000..b216d2d85a --- /dev/null +++ b/internal/test/issues/issue-1182/pkg1.yaml @@ -0,0 +1,16 @@ +openapi: "3.0.1" +info: + version: 1.0.0 + title: Test Server + license: + name: MIT +servers: + - url: http://openapitest.deepmap.ai +paths: + /test: + get: + summary: get test response + operationId: TestGet + responses: + '200': + $ref: "pkg2.yaml#/components/responses/ResponseWithReference" diff --git a/internal/test/issues/issue-1182/pkg1/config.yaml b/internal/test/issues/issue-1182/pkg1/config.yaml new file mode 100644 index 0000000000..a300242fbc --- /dev/null +++ b/internal/test/issues/issue-1182/pkg1/config.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../../../../../configuration-schema.json +package: pkg1 +generate: + echo-server: true + client: true + models: true + embedded-spec: true + strict-server: true +output: pkg1.gen.go +import-mapping: + pkg2.yaml: github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-1182/pkg2 diff --git a/internal/test/issues/issue-1182/pkg1/doc.go b/internal/test/issues/issue-1182/pkg1/doc.go new file mode 100644 index 0000000000..8b57f0836f --- /dev/null +++ b/internal/test/issues/issue-1182/pkg1/doc.go @@ -0,0 +1,3 @@ +package pkg1 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../pkg1.yaml diff --git a/internal/test/issues/issue-1182/pkg1/pkg1.gen.go b/internal/test/issues/issue-1182/pkg1/pkg1.gen.go new file mode 100644 index 0000000000..0b01c5738e --- /dev/null +++ b/internal/test/issues/issue-1182/pkg1/pkg1.gen.go @@ -0,0 +1,428 @@ +// Package pkg1 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package pkg1 + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/labstack/echo/v4" + externalRef0 "github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-1182/pkg2" + strictecho "github.com/oapi-codegen/runtime/strictmiddleware/echo" +) + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // TestGet request + TestGet(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) TestGet(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewTestGetRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewTestGetRequest generates requests for TestGet +func NewTestGetRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/test") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // TestGetWithResponse request + TestGetWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*TestGetResponse, error) +} + +type TestGetResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r TestGetResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r TestGetResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// TestGetWithResponse request returning *TestGetResponse +func (c *ClientWithResponses) TestGetWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*TestGetResponse, error) { + rsp, err := c.TestGet(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseTestGetResponse(rsp) +} + +// ParseTestGetResponse parses an HTTP response from a TestGetWithResponse call +func ParseTestGetResponse(rsp *http.Response) (*TestGetResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &TestGetResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // get test response + // (GET /test) + TestGet(ctx echo.Context) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// TestGet converts echo context to params. +func (w *ServerInterfaceWrapper) TestGet(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.TestGet(ctx) + return err +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.GET(baseURL+"/test", wrapper.TestGet) + +} + +type TestGetRequestObject struct { +} + +type TestGetResponseObject interface { + VisitTestGetResponse(w http.ResponseWriter) error +} + +type TestGet200Response externalRef0.ResponseWithReferenceResponse + +func (response TestGet200Response) VisitTestGetResponse(w http.ResponseWriter) error { + w.WriteHeader(200) + return nil +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + // get test response + // (GET /test) + TestGet(ctx context.Context, request TestGetRequestObject) (TestGetResponseObject, error) +} + +type StrictHandlerFunc = strictecho.StrictEchoHandlerFunc +type StrictMiddlewareFunc = strictecho.StrictEchoMiddlewareFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +// TestGet operation middleware +func (sh *strictHandler) TestGet(ctx echo.Context) error { + var request TestGetRequestObject + + handler := func(ctx echo.Context, request interface{}) (interface{}, error) { + return sh.ssi.TestGet(ctx.Request().Context(), request.(TestGetRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "TestGet") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(TestGetResponseObject); ok { + return validResponse.VisitTestGetResponse(ctx.Response()) + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/3yQMU/DMBCF/4r1YIyStGzemFAHllKJASFknJfGtLEt+1qEqvx35LQUsTCdz3fv7r53", + "gg1jDJ5eMvQJiTkGnzkncbddvq0vP89OhjV7JnrLUu2YbXJRXPDQuFc/UhXeP2hFfQ7ODspllYoqsVMS", + "VJ/CqIwPMjCpaOzObIlpmio434cydu8sfZ43eDMSGo+rDaYK4mRf0g2zqCemIxMqHJny+YJF3dZtaQyR", + "3kQHjbu6rReoEI0MM1EjzFIeW84hRCZTCFbdZfIDBdVfG5ZtW8JtYg+Nm+bXseba1/zjVaHLh3E06Qu6", + "bFbliqtfZ/w8A2XolxMOaQ+NQSTqprnQFEndkXE0sTYO0+v0HQAA//9bF49xvAEAAA==", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + for rawPath, rawFunc := range externalRef0.PathToRawSpec(path.Join(path.Dir(pathToFile), "pkg2.yaml")) { + if _, ok := res[rawPath]; ok { + // it is not possible to compare functions in golang, so always overwrite the old value + } + res[rawPath] = rawFunc + } + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/issues/issue-1182/pkg2.yaml b/internal/test/issues/issue-1182/pkg2.yaml new file mode 100644 index 0000000000..6819d9f7a1 --- /dev/null +++ b/internal/test/issues/issue-1182/pkg2.yaml @@ -0,0 +1,12 @@ +openapi: "3.0.1" +info: + version: 1.0.0 + title: Test Server + license: + name: MIT +servers: + - url: http://openapitest.deepmap.ai +components: + responses: + ResponseWithReference: + description: A response object which is referred to from another package diff --git a/internal/test/issues/issue-1182/pkg2/config.yaml b/internal/test/issues/issue-1182/pkg2/config.yaml new file mode 100644 index 0000000000..e61fd48440 --- /dev/null +++ b/internal/test/issues/issue-1182/pkg2/config.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../../../../../configuration-schema.json +package: pkg2 +generate: + echo-server: true + client: true + models: true + embedded-spec: true + strict-server: true +output: pkg2.gen.go +output-options: + skip-prune: true diff --git a/internal/test/issues/issue-1182/pkg2/doc.go b/internal/test/issues/issue-1182/pkg2/doc.go new file mode 100644 index 0000000000..68cb0b78a1 --- /dev/null +++ b/internal/test/issues/issue-1182/pkg2/doc.go @@ -0,0 +1,3 @@ +package pkg2 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../pkg2.yaml diff --git a/internal/test/issues/issue-1182/pkg2/pkg2.gen.go b/internal/test/issues/issue-1182/pkg2/pkg2.gen.go new file mode 100644 index 0000000000..51eece40aa --- /dev/null +++ b/internal/test/issues/issue-1182/pkg2/pkg2.gen.go @@ -0,0 +1,275 @@ +// Package pkg2 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package pkg2 + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/labstack/echo/v4" + strictecho "github.com/oapi-codegen/runtime/strictmiddleware/echo" +) + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + +} + +type ResponseWithReferenceResponse struct { +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { +} + +type StrictHandlerFunc = strictecho.StrictEchoHandlerFunc +type StrictMiddlewareFunc = strictecho.StrictEchoMiddlewareFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/zSOMU/DMBSE/4p1c+S2YvPGyMBSKjEgBuNcsCGxLb/XMkT+7yihjJ/e++5uRShLLZlZ", + "BW5Fo9SShTuc7/CaNJ45sTEHboeRElqqmkqGw6P5t0z5+GJQ8xNTiCaJaZvVOBotZmplMT4XjWym+vDt", + "P4ne+4CUp7LFzikwy96Q/UI4PD9d0Ado0nnDC0XNC9uNDQNubPK34GSP9rg9lsrsa4LDgz3aEwZUr1Hg", + "8nWeB8iuCtzbimub4RBVqzsc7p5S1I5kXXy1PqG/998AAAD//4Nm84whAQAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/issues/issue-1189/config.yaml b/internal/test/issues/issue-1189/config.yaml new file mode 100644 index 0000000000..ddc5076566 --- /dev/null +++ b/internal/test/issues/issue-1189/config.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: param +generate: + echo-server: true + client: true + models: true + embedded-spec: true +output: issue1189.gen.go diff --git a/internal/test/issues/issue-1189/doc.go b/internal/test/issues/issue-1189/doc.go new file mode 100644 index 0000000000..e64506f488 --- /dev/null +++ b/internal/test/issues/issue-1189/doc.go @@ -0,0 +1,3 @@ +package param + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml issue1189.yaml diff --git a/internal/test/issues/issue-1189/issue1189.gen.go b/internal/test/issues/issue-1189/issue1189.gen.go new file mode 100644 index 0000000000..c6e0579fd5 --- /dev/null +++ b/internal/test/issues/issue-1189/issue1189.gen.go @@ -0,0 +1,586 @@ +// Package param provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package param + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/labstack/echo/v4" + "github.com/oapi-codegen/runtime" +) + +// Defines values for TestFieldA1. +const ( + TestFieldA1Bar TestFieldA1 = "bar" + TestFieldA1Foo TestFieldA1 = "foo" +) + +// Valid indicates whether the value is a known member of the TestFieldA1 enum. +func (e TestFieldA1) Valid() bool { + switch e { + case TestFieldA1Bar: + return true + case TestFieldA1Foo: + return true + default: + return false + } +} + +// Defines values for TestFieldB. +const ( + TestFieldBBar TestFieldB = "bar" + TestFieldBFoo TestFieldB = "foo" +) + +// Valid indicates whether the value is a known member of the TestFieldB enum. +func (e TestFieldB) Valid() bool { + switch e { + case TestFieldBBar: + return true + case TestFieldBFoo: + return true + default: + return false + } +} + +// Defines values for TestFieldC1. +const ( + Bar TestFieldC1 = "bar" + Foo TestFieldC1 = "foo" +) + +// Valid indicates whether the value is a known member of the TestFieldC1 enum. +func (e TestFieldC1) Valid() bool { + switch e { + case Bar: + return true + case Foo: + return true + default: + return false + } +} + +// Test defines model for test. +type Test struct { + FieldA *Test_FieldA `json:"fieldA,omitempty"` + FieldB *TestFieldB `json:"fieldB,omitempty"` + FieldC *Test_FieldC `json:"fieldC,omitempty"` +} + +// TestFieldA0 defines model for . +type TestFieldA0 = string + +// TestFieldA1 defines model for Test.FieldA.1. +type TestFieldA1 string + +// Test_FieldA defines model for Test.FieldA. +type Test_FieldA struct { + union json.RawMessage +} + +// TestFieldB defines model for Test.FieldB. +type TestFieldB string + +// TestFieldC0 defines model for . +type TestFieldC0 = string + +// TestFieldC1 defines model for Test.FieldC.1. +type TestFieldC1 string + +// Test_FieldC defines model for Test.FieldC. +type Test_FieldC struct { + union json.RawMessage +} + +// AsTestFieldA0 returns the union data inside the Test_FieldA as a TestFieldA0 +func (t Test_FieldA) AsTestFieldA0() (TestFieldA0, error) { + var body TestFieldA0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTestFieldA0 overwrites any union data inside the Test_FieldA as the provided TestFieldA0 +func (t *Test_FieldA) FromTestFieldA0(v TestFieldA0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTestFieldA0 performs a merge with any union data inside the Test_FieldA, using the provided TestFieldA0 +func (t *Test_FieldA) MergeTestFieldA0(v TestFieldA0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsTestFieldA1 returns the union data inside the Test_FieldA as a TestFieldA1 +func (t Test_FieldA) AsTestFieldA1() (TestFieldA1, error) { + var body TestFieldA1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTestFieldA1 overwrites any union data inside the Test_FieldA as the provided TestFieldA1 +func (t *Test_FieldA) FromTestFieldA1(v TestFieldA1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTestFieldA1 performs a merge with any union data inside the Test_FieldA, using the provided TestFieldA1 +func (t *Test_FieldA) MergeTestFieldA1(v TestFieldA1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t Test_FieldA) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *Test_FieldA) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsTestFieldC0 returns the union data inside the Test_FieldC as a TestFieldC0 +func (t Test_FieldC) AsTestFieldC0() (TestFieldC0, error) { + var body TestFieldC0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTestFieldC0 overwrites any union data inside the Test_FieldC as the provided TestFieldC0 +func (t *Test_FieldC) FromTestFieldC0(v TestFieldC0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTestFieldC0 performs a merge with any union data inside the Test_FieldC, using the provided TestFieldC0 +func (t *Test_FieldC) MergeTestFieldC0(v TestFieldC0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsTestFieldC1 returns the union data inside the Test_FieldC as a TestFieldC1 +func (t Test_FieldC) AsTestFieldC1() (TestFieldC1, error) { + var body TestFieldC1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTestFieldC1 overwrites any union data inside the Test_FieldC as the provided TestFieldC1 +func (t *Test_FieldC) FromTestFieldC1(v TestFieldC1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTestFieldC1 performs a merge with any union data inside the Test_FieldC, using the provided TestFieldC1 +func (t *Test_FieldC) MergeTestFieldC1(v TestFieldC1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t Test_FieldC) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *Test_FieldC) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // Test request + Test(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) Test(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewTestRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewTestRequest generates requests for Test +func NewTestRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/test") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // TestWithResponse request + TestWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*TestResponse, error) +} + +type TestResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Test +} + +// Status returns HTTPResponse.Status +func (r TestResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r TestResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// TestWithResponse request returning *TestResponse +func (c *ClientWithResponses) TestWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*TestResponse, error) { + rsp, err := c.Test(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseTestResponse(rsp) +} + +// ParseTestResponse parses an HTTP response from a TestWithResponse call +func ParseTestResponse(rsp *http.Response) (*TestResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &TestResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Test + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /test) + Test(ctx echo.Context) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// Test converts echo context to params. +func (w *ServerInterfaceWrapper) Test(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.Test(ctx) + return err +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.GET(baseURL+"/test", wrapper.Test) + +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/6yRsU4zMRCE32X+v7RyJ+jcARUVDV2UwlzWiZFvd2Vviuh0747sI4pEC9vMyPZ+lmYW", + "TDKrMLFV+AV1OtMcujWq1lSLKBVL1E9jonx8ai7w9S3C7xfYVQke1UriE1a3gPgyw+8RReDwEQoO7uez", + "w+o22nOn5fw3tJdGE6Zf0to4JI4Cz5ecHUSJgyZ4PO7G3QgHDXbuoQy3rE7UpQUWLAm/HuHx3i4dClUV", + "rluMD+PYZBI24r4TVHOa+tbwWYXvbTT3v1CEx7/hXtfw3dX2+XqbrwAAAP//gr+fh9IBAAA=", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/issues/issue-1189/issue1189.yaml b/internal/test/issues/issue-1189/issue1189.yaml new file mode 100644 index 0000000000..e84bcea034 --- /dev/null +++ b/internal/test/issues/issue-1189/issue1189.yaml @@ -0,0 +1,36 @@ +openapi: 3.0.0 +paths: + /test: + get: + operationId: Test + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/test' +components: + schemas: + test: + properties: + fieldA: + anyOf: + - type: string + - type: string + enum: + - foo + - bar + fieldB: + allOf: + - type: string + - type: string + enum: + - foo + - bar + fieldC: + oneOf: + - type: string + - type: string + enum: + - foo + - bar diff --git a/internal/test/issues/issue-1208-1209/config.yaml b/internal/test/issues/issue-1208-1209/config.yaml new file mode 100644 index 0000000000..52a520aa38 --- /dev/null +++ b/internal/test/issues/issue-1208-1209/config.yaml @@ -0,0 +1,9 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: multijson +generate: + client: true + models: true + embedded-spec: true + gin-server: true + strict-server: true +output: issue-multi-json.gen.go diff --git a/internal/test/issues/issue-1208-1209/doc.go b/internal/test/issues/issue-1208-1209/doc.go new file mode 100644 index 0000000000..7384a2ab9f --- /dev/null +++ b/internal/test/issues/issue-1208-1209/doc.go @@ -0,0 +1,3 @@ +package multijson + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml issue-multi-json.yaml diff --git a/internal/test/issues/issue-1208-1209/issue-multi-json.gen.go b/internal/test/issues/issue-1208-1209/issue-multi-json.gen.go new file mode 100644 index 0000000000..780d45e6a7 --- /dev/null +++ b/internal/test/issues/issue-1208-1209/issue-multi-json.gen.go @@ -0,0 +1,516 @@ +// Package multijson provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package multijson + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/gin-gonic/gin" + strictgin "github.com/oapi-codegen/runtime/strictmiddleware/gin" +) + +// Bar defines model for bar. +type Bar struct { + Field2 *string `json:"field2,omitempty"` +} + +// Foo defines model for foo. +type Foo struct { + Field1 *string `json:"field1,omitempty"` +} + +// BazApplicationBarPlusJSON defines model for baz. +type BazApplicationBarPlusJSON = Bar + +// BazApplicationFooPlusJSON defines model for baz. +type BazApplicationFooPlusJSON = Foo + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // Test request + Test(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) Test(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewTestRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewTestRequest generates requests for Test +func NewTestRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/test") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // TestWithResponse request + TestWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*TestResponse, error) +} + +type TestResponse struct { + Body []byte + HTTPResponse *http.Response + ApplicationbarJSON200 *Bar + ApplicationfooJSON200 *Foo + ApplicationbarJSON201 *BazApplicationBarPlusJSON + ApplicationfooJSON201 *BazApplicationFooPlusJSON +} + +// Status returns HTTPResponse.Status +func (r TestResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r TestResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// TestWithResponse request returning *TestResponse +func (c *ClientWithResponses) TestWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*TestResponse, error) { + rsp, err := c.Test(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseTestResponse(rsp) +} + +// ParseTestResponse parses an HTTP response from a TestWithResponse call +func ParseTestResponse(rsp *http.Response) (*TestResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &TestResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case rsp.Header.Get("Content-Type") == "application/bar+json" && rsp.StatusCode == 200: + var dest Bar + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.ApplicationbarJSON200 = &dest + + case rsp.Header.Get("Content-Type") == "application/bar+json" && rsp.StatusCode == 201: + var dest BazApplicationBarPlusJSON + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.ApplicationbarJSON201 = &dest + + case rsp.Header.Get("Content-Type") == "application/foo+json" && rsp.StatusCode == 200: + var dest Foo + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.ApplicationfooJSON200 = &dest + + case rsp.Header.Get("Content-Type") == "application/foo+json" && rsp.StatusCode == 201: + var dest BazApplicationFooPlusJSON + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.ApplicationfooJSON201 = &dest + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /test) + Test(c *gin.Context) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) +} + +type MiddlewareFunc func(c *gin.Context) + +// Test operation middleware +func (siw *ServerInterfaceWrapper) Test(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.Test(c) +} + +// GinServerOptions provides options for the Gin server. +type GinServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router gin.IRouter, si ServerInterface) { + RegisterHandlersWithOptions(router, si, GinServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options GinServerOptions) { + errorHandler := options.ErrorHandler + if errorHandler == nil { + errorHandler = func(c *gin.Context, err error, statusCode int) { + c.JSON(statusCode, gin.H{"msg": err.Error()}) + } + } + + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandler: errorHandler, + } + + router.GET(options.BaseURL+"/test", wrapper.Test) +} + +type BazApplicationBarPlusJSONResponse Bar +type BazApplicationFooPlusJSONResponse Foo + +type TestRequestObject struct { +} + +type TestResponseObject interface { + VisitTestResponse(w http.ResponseWriter) error +} + +type Test200ApplicationBarPlusJSONResponse Bar + +func (response Test200ApplicationBarPlusJSONResponse) VisitTestResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/bar+json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type Test200ApplicationFooPlusJSONResponse Foo + +func (response Test200ApplicationFooPlusJSONResponse) VisitTestResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/foo+json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type Test201ApplicationBarPlusJSONResponse struct { + BazApplicationBarPlusJSONResponse +} + +func (response Test201ApplicationBarPlusJSONResponse) VisitTestResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/bar+json") + w.WriteHeader(201) + + return json.NewEncoder(w).Encode(response) +} + +type Test201ApplicationFooPlusJSONResponse struct { + BazApplicationFooPlusJSONResponse +} + +func (response Test201ApplicationFooPlusJSONResponse) VisitTestResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/foo+json") + w.WriteHeader(201) + + return json.NewEncoder(w).Encode(response) +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (GET /test) + Test(ctx context.Context, request TestRequestObject) (TestResponseObject, error) +} + +type StrictHandlerFunc = strictgin.StrictGinHandlerFunc +type StrictMiddlewareFunc = strictgin.StrictGinMiddlewareFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +// Test operation middleware +func (sh *strictHandler) Test(ctx *gin.Context) { + var request TestRequestObject + + handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { + return sh.ssi.Test(ctx, request.(TestRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "Test") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.Error(err) + ctx.Status(http.StatusInternalServerError) + } else if validResponse, ok := response.(TestResponseObject); ok { + if err := validResponse.VisitTestResponse(ctx.Writer); err != nil { + ctx.Error(err) + } + } else if response != nil { + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) + } +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/8ySQU4DMQxF7/JhR9Skwy43YM8F0qnTBk1tKwkLqObuyGkFqlQW7JiNPXHeT/5Xzpjl", + "pMLEvSGeUampcKPxs0ufVmbhTtytTapLmVMvwn6X6tNbE7b1Nh/plKx7rJQR8eB/dP1l2ozAuroblSzy", + "R5UsgtU+dwWud61WtIpS7eViIBda9pN1/UMJEa3XwgcM2HTuE9u7hDGFsyDy+7I4iBInLYh43oRNgIOm", + "fhwqvlMbeR1oFDth2H3ZI+LVhu426imEfxy1wxS2v23+9uHtvYyg1q8AAAD//25zbQ5XAgAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/issues/issue-1208-1209/issue-multi-json.yaml b/internal/test/issues/issue-1208-1209/issue-multi-json.yaml new file mode 100644 index 0000000000..8a52ac0c0c --- /dev/null +++ b/internal/test/issues/issue-1208-1209/issue-multi-json.yaml @@ -0,0 +1,35 @@ +openapi: 3.0.0 +paths: + /test: + get: + operationId: Test + responses: + "200": + content: + application/foo+json: + schema: + $ref: '#/components/schemas/foo' + application/bar+json: + schema: + $ref: '#/components/schemas/bar' + "201": + $ref: '#/components/responses/baz' +components: + schemas: + foo: + properties: + field1: + type: string + bar: + properties: + field2: + type: string + responses: + baz: + content: + application/foo+json: + schema: + $ref: '#/components/schemas/foo' + application/bar+json: + schema: + $ref: '#/components/schemas/bar' diff --git a/internal/test/issues/issue-1208-1209/issue-multi-json_test.go b/internal/test/issues/issue-1208-1209/issue-multi-json_test.go new file mode 100644 index 0000000000..2c379909e9 --- /dev/null +++ b/internal/test/issues/issue-1208-1209/issue-multi-json_test.go @@ -0,0 +1,114 @@ +package multijson_test + +import ( + "bytes" + "io" + "net/http" + "testing" + + multijson "github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-1208-1209" + "github.com/stretchr/testify/assert" +) + +func TestIssueMultiJson(t *testing.T) { + // Status code 200, application/foo+json + bodyFoo := []byte(`{"field1": "foo"}`) + rawResponse := &http.Response{ + StatusCode: 200, + Body: io.NopCloser(bytes.NewReader(bodyFoo)), + Header: http.Header{}, + } + rawResponse.Header.Add("Content-type", "application/foo+json") + + response, err := multijson.ParseTestResponse(rawResponse) + assert.NoError(t, err) + assert.NotNil(t, response.ApplicationfooJSON200) + assert.NotNil(t, response.ApplicationfooJSON200.Field1) + assert.Equal(t, "foo", *response.ApplicationfooJSON200.Field1) + assert.Nil(t, response.ApplicationbarJSON200) + assert.Nil(t, response.ApplicationfooJSON201) + assert.Nil(t, response.ApplicationbarJSON201) + + // Status code 200, application/bar+json + bodyBar := []byte(`{"field2": "bar"}`) + rawResponse = &http.Response{ + StatusCode: 200, + Body: io.NopCloser(bytes.NewReader(bodyBar)), + Header: http.Header{}, + } + rawResponse.Header.Add("Content-type", "application/bar+json") + + response, err = multijson.ParseTestResponse(rawResponse) + assert.NoError(t, err) + assert.Nil(t, response.ApplicationfooJSON200) + assert.NotNil(t, response.ApplicationbarJSON200) + assert.NotNil(t, response.ApplicationbarJSON200.Field2) + assert.Equal(t, "bar", *response.ApplicationbarJSON200.Field2) + assert.Nil(t, response.ApplicationfooJSON201) + assert.Nil(t, response.ApplicationbarJSON201) + + // Status code 200, application/qux+json + bodyQux := []byte(`{"field4": "Qux"}`) + rawResponse = &http.Response{ + StatusCode: 200, + Body: io.NopCloser(bytes.NewReader(bodyQux)), + Header: http.Header{}, + } + rawResponse.Header.Add("Content-type", "application/qux+json") + + response, err = multijson.ParseTestResponse(rawResponse) + assert.NoError(t, err) + assert.Nil(t, response.ApplicationfooJSON200) + assert.Nil(t, response.ApplicationbarJSON200) + assert.Nil(t, response.ApplicationfooJSON201) + assert.Nil(t, response.ApplicationbarJSON201) + + // Status code 201, application/foo+json + rawResponse = &http.Response{ + StatusCode: 201, + Body: io.NopCloser(bytes.NewReader(bodyFoo)), + Header: http.Header{}, + } + rawResponse.Header.Add("Content-type", "application/foo+json") + + response, err = multijson.ParseTestResponse(rawResponse) + assert.NoError(t, err) + assert.Nil(t, response.ApplicationfooJSON200) + assert.Nil(t, response.ApplicationbarJSON200) + assert.NotNil(t, response.ApplicationfooJSON201) + assert.NotNil(t, response.ApplicationfooJSON201.Field1) + assert.Equal(t, "foo", *response.ApplicationfooJSON201.Field1) + assert.Nil(t, response.ApplicationbarJSON201) + + // Status code 201, application/bar+json + rawResponse = &http.Response{ + StatusCode: 201, + Body: io.NopCloser(bytes.NewReader(bodyBar)), + Header: http.Header{}, + } + rawResponse.Header.Add("Content-type", "application/bar+json") + + response, err = multijson.ParseTestResponse(rawResponse) + assert.NoError(t, err) + assert.Nil(t, response.ApplicationfooJSON200) + assert.Nil(t, response.ApplicationbarJSON200) + assert.Nil(t, response.ApplicationfooJSON201) + assert.NotNil(t, response.ApplicationbarJSON201) + assert.NotNil(t, response.ApplicationbarJSON201.Field2) + assert.Equal(t, "bar", *response.ApplicationbarJSON201.Field2) + + // Status code 201, application/qux+json + rawResponse = &http.Response{ + StatusCode: 201, + Body: io.NopCloser(bytes.NewReader(bodyQux)), + Header: http.Header{}, + } + rawResponse.Header.Add("Content-type", "application/qux+json") + + response, err = multijson.ParseTestResponse(rawResponse) + assert.NoError(t, err) + assert.Nil(t, response.ApplicationfooJSON200) + assert.Nil(t, response.ApplicationbarJSON200) + assert.Nil(t, response.ApplicationfooJSON201) + assert.Nil(t, response.ApplicationbarJSON201) +} diff --git a/internal/test/issues/issue-1212/pkg1.yaml b/internal/test/issues/issue-1212/pkg1.yaml new file mode 100644 index 0000000000..c079bd6d9f --- /dev/null +++ b/internal/test/issues/issue-1212/pkg1.yaml @@ -0,0 +1,8 @@ +openapi: 3.0.0 +paths: + /test: + get: + operationId: Test + responses: + "200": + $ref: 'pkg2.yaml#/components/responses/test' diff --git a/internal/test/issues/issue-1212/pkg1/config.yaml b/internal/test/issues/issue-1212/pkg1/config.yaml new file mode 100644 index 0000000000..040b8223b7 --- /dev/null +++ b/internal/test/issues/issue-1212/pkg1/config.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../../../../../configuration-schema.json +package: pkg1 +generate: + client: true + models: true + embedded-spec: true + gin-server: true + strict-server: true +output: pkg1.gen.go +import-mapping: + pkg2.yaml: github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-1212/pkg2 diff --git a/internal/test/issues/issue-1212/pkg1/doc.go b/internal/test/issues/issue-1212/pkg1/doc.go new file mode 100644 index 0000000000..8b57f0836f --- /dev/null +++ b/internal/test/issues/issue-1212/pkg1/doc.go @@ -0,0 +1,3 @@ +package pkg1 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../pkg1.yaml diff --git a/internal/test/issues/issue-1212/pkg1/pkg1.gen.go b/internal/test/issues/issue-1212/pkg1/pkg1.gen.go new file mode 100644 index 0000000000..c24850b199 --- /dev/null +++ b/internal/test/issues/issue-1212/pkg1/pkg1.gen.go @@ -0,0 +1,441 @@ +// Package pkg1 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package pkg1 + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "fmt" + "io" + "mime" + "mime/multipart" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/gin-gonic/gin" + externalRef0 "github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-1212/pkg2" + strictgin "github.com/oapi-codegen/runtime/strictmiddleware/gin" +) + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // Test request + Test(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) Test(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewTestRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewTestRequest generates requests for Test +func NewTestRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/test") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // TestWithResponse request + TestWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*TestResponse, error) +} + +type TestResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r TestResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r TestResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// TestWithResponse request returning *TestResponse +func (c *ClientWithResponses) TestWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*TestResponse, error) { + rsp, err := c.Test(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseTestResponse(rsp) +} + +// ParseTestResponse parses an HTTP response from a TestWithResponse call +func ParseTestResponse(rsp *http.Response) (*TestResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &TestResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /test) + Test(c *gin.Context) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) +} + +type MiddlewareFunc func(c *gin.Context) + +// Test operation middleware +func (siw *ServerInterfaceWrapper) Test(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.Test(c) +} + +// GinServerOptions provides options for the Gin server. +type GinServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router gin.IRouter, si ServerInterface) { + RegisterHandlersWithOptions(router, si, GinServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options GinServerOptions) { + errorHandler := options.ErrorHandler + if errorHandler == nil { + errorHandler = func(c *gin.Context, err error, statusCode int) { + c.JSON(statusCode, gin.H{"msg": err.Error()}) + } + } + + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandler: errorHandler, + } + + router.GET(options.BaseURL+"/test", wrapper.Test) +} + +type TestRequestObject struct { +} + +type TestResponseObject interface { + VisitTestResponse(w http.ResponseWriter) error +} + +type Test200MultipartResponse externalRef0.TestMultipartResponse + +func (response Test200MultipartResponse) VisitTestResponse(w http.ResponseWriter) error { + writer := multipart.NewWriter(w) + w.Header().Set("Content-Type", mime.FormatMediaType("multipart/related", map[string]string{"boundary": writer.Boundary()})) + w.WriteHeader(200) + + defer writer.Close() + return response(writer) +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (GET /test) + Test(ctx context.Context, request TestRequestObject) (TestResponseObject, error) +} + +type StrictHandlerFunc = strictgin.StrictGinHandlerFunc +type StrictMiddlewareFunc = strictgin.StrictGinMiddlewareFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +// Test operation middleware +func (sh *strictHandler) Test(ctx *gin.Context) { + var request TestRequestObject + + handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { + return sh.ssi.Test(ctx, request.(TestRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "Test") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.Error(err) + ctx.Status(http.StatusInternalServerError) + } else if validResponse, ok := response.(TestResponseObject); ok { + if err := validResponse.VisitTestResponse(ctx.Writer); err != nil { + ctx.Error(err) + } + } else if response != nil { + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) + } +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/4yRMU/DMBCF/8uDMapD2DwyILGzIze5pAbnzrKvA6r635FtaFSpSM2SO9vfe6d3J4yy", + "RmFizbAnJMpROFNt4tcyfChlLc0orMS1XI9BfXRJTaLglKZymMcDra5iSSIl9U3kMwu/uFTKx0QzLB7M", + "5mkalk312ruEc1eRV5G7kFkE5/Z1vzNss++b7/VAs6cwDaXS70iwyJo8L6gKF83b2NNNrICeZ4HlYwgd", + "JBK76GHxvOt3PTpEp4eqYv7iXKj+ioNTL/w2weK9XHbXSxj6/r8gLu/MtqkWxU8AAAD//+WESHLXAQAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + for rawPath, rawFunc := range externalRef0.PathToRawSpec(path.Join(path.Dir(pathToFile), "pkg2.yaml")) { + if _, ok := res[rawPath]; ok { + // it is not possible to compare functions in golang, so always overwrite the old value + } + res[rawPath] = rawFunc + } + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/issues/issue-1212/pkg2.yaml b/internal/test/issues/issue-1212/pkg2.yaml new file mode 100644 index 0000000000..96941832ca --- /dev/null +++ b/internal/test/issues/issue-1212/pkg2.yaml @@ -0,0 +1,21 @@ +openapi: "3.0.1" +components: + schemas: + foo: + properties: + field1: + type: string + bar: + properties: + field2: + type: string + responses: + test: + content: + multipart/related: + schema: + properties: + jsonFoo: + $ref: '#/components/schemas/foo' + jsonBar: + $ref: '#/components/schemas/bar' diff --git a/internal/test/issues/issue-1212/pkg2/config.yaml b/internal/test/issues/issue-1212/pkg2/config.yaml new file mode 100644 index 0000000000..d3a63e5d40 --- /dev/null +++ b/internal/test/issues/issue-1212/pkg2/config.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../../../../../configuration-schema.json +package: pkg2 +generate: + client: true + models: true + embedded-spec: true + gin-server: true + strict-server: true +output: pkg2.gen.go +output-options: + skip-prune: true diff --git a/internal/test/issues/issue-1212/pkg2/doc.go b/internal/test/issues/issue-1212/pkg2/doc.go new file mode 100644 index 0000000000..68cb0b78a1 --- /dev/null +++ b/internal/test/issues/issue-1212/pkg2/doc.go @@ -0,0 +1,3 @@ +package pkg2 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../pkg2.yaml diff --git a/internal/test/issues/issue-1212/pkg2/pkg2.gen.go b/internal/test/issues/issue-1212/pkg2/pkg2.gen.go new file mode 100644 index 0000000000..5a994ed06f --- /dev/null +++ b/internal/test/issues/issue-1212/pkg2/pkg2.gen.go @@ -0,0 +1,280 @@ +// Package pkg2 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package pkg2 + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "fmt" + "mime/multipart" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/gin-gonic/gin" + strictgin "github.com/oapi-codegen/runtime/strictmiddleware/gin" +) + +// Bar defines model for bar. +type Bar struct { + Field2 *string `json:"field2,omitempty"` +} + +// Foo defines model for foo. +type Foo struct { + Field1 *string `json:"field1,omitempty"` +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) +} + +type MiddlewareFunc func(c *gin.Context) + +// GinServerOptions provides options for the Gin server. +type GinServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router gin.IRouter, si ServerInterface) { + RegisterHandlersWithOptions(router, si, GinServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options GinServerOptions) { + +} + +type TestMultipartResponse func(writer *multipart.Writer) error + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { +} + +type StrictHandlerFunc = strictgin.StrictGinHandlerFunc +type StrictMiddlewareFunc = strictgin.StrictGinMiddlewareFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/4SPwU4EIQyG36V6JIurN44efA92trgYpm3a7sFM5t0NYGJMJlku/MD/5aMbLLwKE5Ib", + "pA0UTZgMx8HRvO8LkyONuN6bV8nqUbFlx2u/tOWGa+5JlAXV6+S/jOk9a4/PigUSPMU/XZyYxUtW2MNo", + "fzA/ahdm2OcKv+Yhu0zR/x+Uiu36Omb5FoQE5lrpEwZcpuyAOB8SnalUGBLdWwvAgpSlQoK308vpDAEk", + "+83m8/4TAAD//08ZxXtaAQAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/issues/issue-1219/config.yaml b/internal/test/issues/issue-1219/config.yaml new file mode 100644 index 0000000000..91e041af5f --- /dev/null +++ b/internal/test/issues/issue-1219/config.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: issue1219 +generate: + models: true +output-options: + skip-prune: true +output: issue.gen.go diff --git a/internal/test/issues/issue-1219/doc.go b/internal/test/issues/issue-1219/doc.go new file mode 100644 index 0000000000..7e1118e5c9 --- /dev/null +++ b/internal/test/issues/issue-1219/doc.go @@ -0,0 +1,3 @@ +package issue1219 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml issue.yaml diff --git a/internal/test/issues/issue-1219/issue.gen.go b/internal/test/issues/issue-1219/issue.gen.go new file mode 100644 index 0000000000..2beeedc59c --- /dev/null +++ b/internal/test/issues/issue-1219/issue.gen.go @@ -0,0 +1,1311 @@ +// Package issue1219 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue1219 + +import ( + "encoding/json" + "fmt" +) + +// DefaultAdditional1 defines model for DefaultAdditional1. +type DefaultAdditional1 struct { + Field1 *int `json:"field1,omitempty"` + Field2 *string `json:"field2,omitempty"` +} + +// DefaultAdditional2 defines model for DefaultAdditional2. +type DefaultAdditional2 struct { + FieldA *int `json:"fieldA,omitempty"` + FieldB *string `json:"fieldB,omitempty"` +} + +// MergeDefaultDefault defines model for MergeDefaultDefault. +type MergeDefaultDefault struct { + Field1 *int `json:"field1,omitempty"` + Field2 *string `json:"field2,omitempty"` + FieldA *int `json:"fieldA,omitempty"` + FieldB *string `json:"fieldB,omitempty"` +} + +// MergeDefaultWithAny defines model for MergeDefaultWithAny. +type MergeDefaultWithAny struct { + Field1 *int `json:"field1,omitempty"` + Field2 *string `json:"field2,omitempty"` + FieldA *int `json:"fieldA,omitempty"` + FieldB *string `json:"fieldB,omitempty"` + AdditionalProperties map[string]interface{} `json:"-"` +} + +// MergeDefaultWithString defines model for MergeDefaultWithString. +type MergeDefaultWithString struct { + Field1 *int `json:"field1,omitempty"` + Field2 *string `json:"field2,omitempty"` + FieldA *int `json:"fieldA,omitempty"` + FieldB *string `json:"fieldB,omitempty"` + AdditionalProperties map[string]string `json:"-"` +} + +// MergeDefaultWithout defines model for MergeDefaultWithout. +type MergeDefaultWithout struct { + Field1 *int `json:"field1,omitempty"` + Field2 *string `json:"field2,omitempty"` + FieldA *int `json:"fieldA,omitempty"` + FieldB *string `json:"fieldB,omitempty"` +} + +// MergeWithAnyDefault defines model for MergeWithAnyDefault. +type MergeWithAnyDefault struct { + Field1 *int `json:"field1,omitempty"` + Field2 *string `json:"field2,omitempty"` + FieldA *int `json:"fieldA,omitempty"` + FieldB *string `json:"fieldB,omitempty"` + AdditionalProperties map[string]interface{} `json:"-"` +} + +// MergeWithAnyWithAny defines model for MergeWithAnyWithAny. +type MergeWithAnyWithAny struct { + Field1 *int `json:"field1,omitempty"` + Field2 *string `json:"field2,omitempty"` + FieldA *int `json:"fieldA,omitempty"` + FieldB *string `json:"fieldB,omitempty"` + AdditionalProperties map[string]interface{} `json:"-"` +} + +// MergeWithAnyWithString defines model for MergeWithAnyWithString. +type MergeWithAnyWithString struct { + Field1 *int `json:"field1,omitempty"` + Field2 *string `json:"field2,omitempty"` + FieldA *int `json:"fieldA,omitempty"` + FieldB *string `json:"fieldB,omitempty"` + AdditionalProperties map[string]string `json:"-"` +} + +// MergeWithAnyWithout defines model for MergeWithAnyWithout. +type MergeWithAnyWithout struct { + Field1 *int `json:"field1,omitempty"` + Field2 *string `json:"field2,omitempty"` + FieldA *int `json:"fieldA,omitempty"` + FieldB *string `json:"fieldB,omitempty"` +} + +// MergeWithStringDefault defines model for MergeWithStringDefault. +type MergeWithStringDefault struct { + Field1 *int `json:"field1,omitempty"` + Field2 *string `json:"field2,omitempty"` + FieldA *int `json:"fieldA,omitempty"` + FieldB *string `json:"fieldB,omitempty"` + AdditionalProperties map[string]string `json:"-"` +} + +// MergeWithStringWithAny defines model for MergeWithStringWithAny. +type MergeWithStringWithAny struct { + Field1 *int `json:"field1,omitempty"` + Field2 *string `json:"field2,omitempty"` + FieldA *int `json:"fieldA,omitempty"` + FieldB *string `json:"fieldB,omitempty"` + AdditionalProperties map[string]string `json:"-"` +} + +// MergeWithStringWithout defines model for MergeWithStringWithout. +type MergeWithStringWithout struct { + Field1 *int `json:"field1,omitempty"` + Field2 *string `json:"field2,omitempty"` + FieldA *int `json:"fieldA,omitempty"` + FieldB *string `json:"fieldB,omitempty"` +} + +// MergeWithoutDefault defines model for MergeWithoutDefault. +type MergeWithoutDefault struct { + Field1 *int `json:"field1,omitempty"` + Field2 *string `json:"field2,omitempty"` + FieldA *int `json:"fieldA,omitempty"` + FieldB *string `json:"fieldB,omitempty"` +} + +// MergeWithoutWithAny defines model for MergeWithoutWithAny. +type MergeWithoutWithAny struct { + Field1 *int `json:"field1,omitempty"` + Field2 *string `json:"field2,omitempty"` + FieldA *int `json:"fieldA,omitempty"` + FieldB *string `json:"fieldB,omitempty"` +} + +// MergeWithoutWithString defines model for MergeWithoutWithString. +type MergeWithoutWithString struct { + Field1 *int `json:"field1,omitempty"` + Field2 *string `json:"field2,omitempty"` + FieldA *int `json:"fieldA,omitempty"` + FieldB *string `json:"fieldB,omitempty"` +} + +// MergeWithoutWithout defines model for MergeWithoutWithout. +type MergeWithoutWithout struct { + Field1 *int `json:"field1,omitempty"` + Field2 *string `json:"field2,omitempty"` + FieldA *int `json:"fieldA,omitempty"` + FieldB *string `json:"fieldB,omitempty"` +} + +// WithAnyAdditional1 defines model for WithAnyAdditional1. +type WithAnyAdditional1 struct { + Field1 *int `json:"field1,omitempty"` + Field2 *string `json:"field2,omitempty"` + AdditionalProperties map[string]interface{} `json:"-"` +} + +// WithAnyAdditional2 defines model for WithAnyAdditional2. +type WithAnyAdditional2 struct { + FieldA *int `json:"fieldA,omitempty"` + FieldB *string `json:"fieldB,omitempty"` + AdditionalProperties map[string]interface{} `json:"-"` +} + +// WithStringAdditional1 defines model for WithStringAdditional1. +type WithStringAdditional1 struct { + Field1 *int `json:"field1,omitempty"` + Field2 *string `json:"field2,omitempty"` + AdditionalProperties map[string]string `json:"-"` +} + +// WithStringAdditional2 defines model for WithStringAdditional2. +type WithStringAdditional2 struct { + FieldA *int `json:"fieldA,omitempty"` + FieldB *string `json:"fieldB,omitempty"` + AdditionalProperties map[string]string `json:"-"` +} + +// WithoutAdditional1 defines model for WithoutAdditional1. +type WithoutAdditional1 struct { + Field1 *int `json:"field1,omitempty"` + Field2 *string `json:"field2,omitempty"` +} + +// WithoutAdditional2 defines model for WithoutAdditional2. +type WithoutAdditional2 struct { + FieldA *int `json:"fieldA,omitempty"` + FieldB *string `json:"fieldB,omitempty"` +} + +// Getter for additional properties for MergeDefaultWithAny. Returns the specified +// element and whether it was found +func (a MergeDefaultWithAny) Get(fieldName string) (value interface{}, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for MergeDefaultWithAny +func (a *MergeDefaultWithAny) Set(fieldName string, value interface{}) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]interface{}) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for MergeDefaultWithAny to handle AdditionalProperties +func (a *MergeDefaultWithAny) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["field1"]; found { + err = json.Unmarshal(raw, &a.Field1) + if err != nil { + return fmt.Errorf("error reading 'field1': %w", err) + } + delete(object, "field1") + } + + if raw, found := object["field2"]; found { + err = json.Unmarshal(raw, &a.Field2) + if err != nil { + return fmt.Errorf("error reading 'field2': %w", err) + } + delete(object, "field2") + } + + if raw, found := object["fieldA"]; found { + err = json.Unmarshal(raw, &a.FieldA) + if err != nil { + return fmt.Errorf("error reading 'fieldA': %w", err) + } + delete(object, "fieldA") + } + + if raw, found := object["fieldB"]; found { + err = json.Unmarshal(raw, &a.FieldB) + if err != nil { + return fmt.Errorf("error reading 'fieldB': %w", err) + } + delete(object, "fieldB") + } + + if len(object) != 0 { + a.AdditionalProperties = make(map[string]interface{}) + for fieldName, fieldBuf := range object { + var fieldVal interface{} + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for MergeDefaultWithAny to handle AdditionalProperties +func (a MergeDefaultWithAny) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + if a.Field1 != nil { + object["field1"], err = json.Marshal(a.Field1) + if err != nil { + return nil, fmt.Errorf("error marshaling 'field1': %w", err) + } + } + + if a.Field2 != nil { + object["field2"], err = json.Marshal(a.Field2) + if err != nil { + return nil, fmt.Errorf("error marshaling 'field2': %w", err) + } + } + + if a.FieldA != nil { + object["fieldA"], err = json.Marshal(a.FieldA) + if err != nil { + return nil, fmt.Errorf("error marshaling 'fieldA': %w", err) + } + } + + if a.FieldB != nil { + object["fieldB"], err = json.Marshal(a.FieldB) + if err != nil { + return nil, fmt.Errorf("error marshaling 'fieldB': %w", err) + } + } + + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) +} + +// Getter for additional properties for MergeDefaultWithString. Returns the specified +// element and whether it was found +func (a MergeDefaultWithString) Get(fieldName string) (value string, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for MergeDefaultWithString +func (a *MergeDefaultWithString) Set(fieldName string, value string) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]string) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for MergeDefaultWithString to handle AdditionalProperties +func (a *MergeDefaultWithString) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["field1"]; found { + err = json.Unmarshal(raw, &a.Field1) + if err != nil { + return fmt.Errorf("error reading 'field1': %w", err) + } + delete(object, "field1") + } + + if raw, found := object["field2"]; found { + err = json.Unmarshal(raw, &a.Field2) + if err != nil { + return fmt.Errorf("error reading 'field2': %w", err) + } + delete(object, "field2") + } + + if raw, found := object["fieldA"]; found { + err = json.Unmarshal(raw, &a.FieldA) + if err != nil { + return fmt.Errorf("error reading 'fieldA': %w", err) + } + delete(object, "fieldA") + } + + if raw, found := object["fieldB"]; found { + err = json.Unmarshal(raw, &a.FieldB) + if err != nil { + return fmt.Errorf("error reading 'fieldB': %w", err) + } + delete(object, "fieldB") + } + + if len(object) != 0 { + a.AdditionalProperties = make(map[string]string) + for fieldName, fieldBuf := range object { + var fieldVal string + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for MergeDefaultWithString to handle AdditionalProperties +func (a MergeDefaultWithString) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + if a.Field1 != nil { + object["field1"], err = json.Marshal(a.Field1) + if err != nil { + return nil, fmt.Errorf("error marshaling 'field1': %w", err) + } + } + + if a.Field2 != nil { + object["field2"], err = json.Marshal(a.Field2) + if err != nil { + return nil, fmt.Errorf("error marshaling 'field2': %w", err) + } + } + + if a.FieldA != nil { + object["fieldA"], err = json.Marshal(a.FieldA) + if err != nil { + return nil, fmt.Errorf("error marshaling 'fieldA': %w", err) + } + } + + if a.FieldB != nil { + object["fieldB"], err = json.Marshal(a.FieldB) + if err != nil { + return nil, fmt.Errorf("error marshaling 'fieldB': %w", err) + } + } + + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) +} + +// Getter for additional properties for MergeWithAnyDefault. Returns the specified +// element and whether it was found +func (a MergeWithAnyDefault) Get(fieldName string) (value interface{}, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for MergeWithAnyDefault +func (a *MergeWithAnyDefault) Set(fieldName string, value interface{}) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]interface{}) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for MergeWithAnyDefault to handle AdditionalProperties +func (a *MergeWithAnyDefault) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["field1"]; found { + err = json.Unmarshal(raw, &a.Field1) + if err != nil { + return fmt.Errorf("error reading 'field1': %w", err) + } + delete(object, "field1") + } + + if raw, found := object["field2"]; found { + err = json.Unmarshal(raw, &a.Field2) + if err != nil { + return fmt.Errorf("error reading 'field2': %w", err) + } + delete(object, "field2") + } + + if raw, found := object["fieldA"]; found { + err = json.Unmarshal(raw, &a.FieldA) + if err != nil { + return fmt.Errorf("error reading 'fieldA': %w", err) + } + delete(object, "fieldA") + } + + if raw, found := object["fieldB"]; found { + err = json.Unmarshal(raw, &a.FieldB) + if err != nil { + return fmt.Errorf("error reading 'fieldB': %w", err) + } + delete(object, "fieldB") + } + + if len(object) != 0 { + a.AdditionalProperties = make(map[string]interface{}) + for fieldName, fieldBuf := range object { + var fieldVal interface{} + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for MergeWithAnyDefault to handle AdditionalProperties +func (a MergeWithAnyDefault) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + if a.Field1 != nil { + object["field1"], err = json.Marshal(a.Field1) + if err != nil { + return nil, fmt.Errorf("error marshaling 'field1': %w", err) + } + } + + if a.Field2 != nil { + object["field2"], err = json.Marshal(a.Field2) + if err != nil { + return nil, fmt.Errorf("error marshaling 'field2': %w", err) + } + } + + if a.FieldA != nil { + object["fieldA"], err = json.Marshal(a.FieldA) + if err != nil { + return nil, fmt.Errorf("error marshaling 'fieldA': %w", err) + } + } + + if a.FieldB != nil { + object["fieldB"], err = json.Marshal(a.FieldB) + if err != nil { + return nil, fmt.Errorf("error marshaling 'fieldB': %w", err) + } + } + + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) +} + +// Getter for additional properties for MergeWithAnyWithAny. Returns the specified +// element and whether it was found +func (a MergeWithAnyWithAny) Get(fieldName string) (value interface{}, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for MergeWithAnyWithAny +func (a *MergeWithAnyWithAny) Set(fieldName string, value interface{}) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]interface{}) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for MergeWithAnyWithAny to handle AdditionalProperties +func (a *MergeWithAnyWithAny) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["field1"]; found { + err = json.Unmarshal(raw, &a.Field1) + if err != nil { + return fmt.Errorf("error reading 'field1': %w", err) + } + delete(object, "field1") + } + + if raw, found := object["field2"]; found { + err = json.Unmarshal(raw, &a.Field2) + if err != nil { + return fmt.Errorf("error reading 'field2': %w", err) + } + delete(object, "field2") + } + + if raw, found := object["fieldA"]; found { + err = json.Unmarshal(raw, &a.FieldA) + if err != nil { + return fmt.Errorf("error reading 'fieldA': %w", err) + } + delete(object, "fieldA") + } + + if raw, found := object["fieldB"]; found { + err = json.Unmarshal(raw, &a.FieldB) + if err != nil { + return fmt.Errorf("error reading 'fieldB': %w", err) + } + delete(object, "fieldB") + } + + if len(object) != 0 { + a.AdditionalProperties = make(map[string]interface{}) + for fieldName, fieldBuf := range object { + var fieldVal interface{} + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for MergeWithAnyWithAny to handle AdditionalProperties +func (a MergeWithAnyWithAny) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + if a.Field1 != nil { + object["field1"], err = json.Marshal(a.Field1) + if err != nil { + return nil, fmt.Errorf("error marshaling 'field1': %w", err) + } + } + + if a.Field2 != nil { + object["field2"], err = json.Marshal(a.Field2) + if err != nil { + return nil, fmt.Errorf("error marshaling 'field2': %w", err) + } + } + + if a.FieldA != nil { + object["fieldA"], err = json.Marshal(a.FieldA) + if err != nil { + return nil, fmt.Errorf("error marshaling 'fieldA': %w", err) + } + } + + if a.FieldB != nil { + object["fieldB"], err = json.Marshal(a.FieldB) + if err != nil { + return nil, fmt.Errorf("error marshaling 'fieldB': %w", err) + } + } + + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) +} + +// Getter for additional properties for MergeWithAnyWithString. Returns the specified +// element and whether it was found +func (a MergeWithAnyWithString) Get(fieldName string) (value string, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for MergeWithAnyWithString +func (a *MergeWithAnyWithString) Set(fieldName string, value string) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]string) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for MergeWithAnyWithString to handle AdditionalProperties +func (a *MergeWithAnyWithString) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["field1"]; found { + err = json.Unmarshal(raw, &a.Field1) + if err != nil { + return fmt.Errorf("error reading 'field1': %w", err) + } + delete(object, "field1") + } + + if raw, found := object["field2"]; found { + err = json.Unmarshal(raw, &a.Field2) + if err != nil { + return fmt.Errorf("error reading 'field2': %w", err) + } + delete(object, "field2") + } + + if raw, found := object["fieldA"]; found { + err = json.Unmarshal(raw, &a.FieldA) + if err != nil { + return fmt.Errorf("error reading 'fieldA': %w", err) + } + delete(object, "fieldA") + } + + if raw, found := object["fieldB"]; found { + err = json.Unmarshal(raw, &a.FieldB) + if err != nil { + return fmt.Errorf("error reading 'fieldB': %w", err) + } + delete(object, "fieldB") + } + + if len(object) != 0 { + a.AdditionalProperties = make(map[string]string) + for fieldName, fieldBuf := range object { + var fieldVal string + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for MergeWithAnyWithString to handle AdditionalProperties +func (a MergeWithAnyWithString) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + if a.Field1 != nil { + object["field1"], err = json.Marshal(a.Field1) + if err != nil { + return nil, fmt.Errorf("error marshaling 'field1': %w", err) + } + } + + if a.Field2 != nil { + object["field2"], err = json.Marshal(a.Field2) + if err != nil { + return nil, fmt.Errorf("error marshaling 'field2': %w", err) + } + } + + if a.FieldA != nil { + object["fieldA"], err = json.Marshal(a.FieldA) + if err != nil { + return nil, fmt.Errorf("error marshaling 'fieldA': %w", err) + } + } + + if a.FieldB != nil { + object["fieldB"], err = json.Marshal(a.FieldB) + if err != nil { + return nil, fmt.Errorf("error marshaling 'fieldB': %w", err) + } + } + + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) +} + +// Getter for additional properties for MergeWithStringDefault. Returns the specified +// element and whether it was found +func (a MergeWithStringDefault) Get(fieldName string) (value string, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for MergeWithStringDefault +func (a *MergeWithStringDefault) Set(fieldName string, value string) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]string) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for MergeWithStringDefault to handle AdditionalProperties +func (a *MergeWithStringDefault) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["field1"]; found { + err = json.Unmarshal(raw, &a.Field1) + if err != nil { + return fmt.Errorf("error reading 'field1': %w", err) + } + delete(object, "field1") + } + + if raw, found := object["field2"]; found { + err = json.Unmarshal(raw, &a.Field2) + if err != nil { + return fmt.Errorf("error reading 'field2': %w", err) + } + delete(object, "field2") + } + + if raw, found := object["fieldA"]; found { + err = json.Unmarshal(raw, &a.FieldA) + if err != nil { + return fmt.Errorf("error reading 'fieldA': %w", err) + } + delete(object, "fieldA") + } + + if raw, found := object["fieldB"]; found { + err = json.Unmarshal(raw, &a.FieldB) + if err != nil { + return fmt.Errorf("error reading 'fieldB': %w", err) + } + delete(object, "fieldB") + } + + if len(object) != 0 { + a.AdditionalProperties = make(map[string]string) + for fieldName, fieldBuf := range object { + var fieldVal string + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for MergeWithStringDefault to handle AdditionalProperties +func (a MergeWithStringDefault) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + if a.Field1 != nil { + object["field1"], err = json.Marshal(a.Field1) + if err != nil { + return nil, fmt.Errorf("error marshaling 'field1': %w", err) + } + } + + if a.Field2 != nil { + object["field2"], err = json.Marshal(a.Field2) + if err != nil { + return nil, fmt.Errorf("error marshaling 'field2': %w", err) + } + } + + if a.FieldA != nil { + object["fieldA"], err = json.Marshal(a.FieldA) + if err != nil { + return nil, fmt.Errorf("error marshaling 'fieldA': %w", err) + } + } + + if a.FieldB != nil { + object["fieldB"], err = json.Marshal(a.FieldB) + if err != nil { + return nil, fmt.Errorf("error marshaling 'fieldB': %w", err) + } + } + + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) +} + +// Getter for additional properties for MergeWithStringWithAny. Returns the specified +// element and whether it was found +func (a MergeWithStringWithAny) Get(fieldName string) (value string, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for MergeWithStringWithAny +func (a *MergeWithStringWithAny) Set(fieldName string, value string) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]string) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for MergeWithStringWithAny to handle AdditionalProperties +func (a *MergeWithStringWithAny) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["field1"]; found { + err = json.Unmarshal(raw, &a.Field1) + if err != nil { + return fmt.Errorf("error reading 'field1': %w", err) + } + delete(object, "field1") + } + + if raw, found := object["field2"]; found { + err = json.Unmarshal(raw, &a.Field2) + if err != nil { + return fmt.Errorf("error reading 'field2': %w", err) + } + delete(object, "field2") + } + + if raw, found := object["fieldA"]; found { + err = json.Unmarshal(raw, &a.FieldA) + if err != nil { + return fmt.Errorf("error reading 'fieldA': %w", err) + } + delete(object, "fieldA") + } + + if raw, found := object["fieldB"]; found { + err = json.Unmarshal(raw, &a.FieldB) + if err != nil { + return fmt.Errorf("error reading 'fieldB': %w", err) + } + delete(object, "fieldB") + } + + if len(object) != 0 { + a.AdditionalProperties = make(map[string]string) + for fieldName, fieldBuf := range object { + var fieldVal string + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for MergeWithStringWithAny to handle AdditionalProperties +func (a MergeWithStringWithAny) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + if a.Field1 != nil { + object["field1"], err = json.Marshal(a.Field1) + if err != nil { + return nil, fmt.Errorf("error marshaling 'field1': %w", err) + } + } + + if a.Field2 != nil { + object["field2"], err = json.Marshal(a.Field2) + if err != nil { + return nil, fmt.Errorf("error marshaling 'field2': %w", err) + } + } + + if a.FieldA != nil { + object["fieldA"], err = json.Marshal(a.FieldA) + if err != nil { + return nil, fmt.Errorf("error marshaling 'fieldA': %w", err) + } + } + + if a.FieldB != nil { + object["fieldB"], err = json.Marshal(a.FieldB) + if err != nil { + return nil, fmt.Errorf("error marshaling 'fieldB': %w", err) + } + } + + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) +} + +// Getter for additional properties for WithAnyAdditional1. Returns the specified +// element and whether it was found +func (a WithAnyAdditional1) Get(fieldName string) (value interface{}, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for WithAnyAdditional1 +func (a *WithAnyAdditional1) Set(fieldName string, value interface{}) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]interface{}) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for WithAnyAdditional1 to handle AdditionalProperties +func (a *WithAnyAdditional1) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["field1"]; found { + err = json.Unmarshal(raw, &a.Field1) + if err != nil { + return fmt.Errorf("error reading 'field1': %w", err) + } + delete(object, "field1") + } + + if raw, found := object["field2"]; found { + err = json.Unmarshal(raw, &a.Field2) + if err != nil { + return fmt.Errorf("error reading 'field2': %w", err) + } + delete(object, "field2") + } + + if len(object) != 0 { + a.AdditionalProperties = make(map[string]interface{}) + for fieldName, fieldBuf := range object { + var fieldVal interface{} + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for WithAnyAdditional1 to handle AdditionalProperties +func (a WithAnyAdditional1) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + if a.Field1 != nil { + object["field1"], err = json.Marshal(a.Field1) + if err != nil { + return nil, fmt.Errorf("error marshaling 'field1': %w", err) + } + } + + if a.Field2 != nil { + object["field2"], err = json.Marshal(a.Field2) + if err != nil { + return nil, fmt.Errorf("error marshaling 'field2': %w", err) + } + } + + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) +} + +// Getter for additional properties for WithAnyAdditional2. Returns the specified +// element and whether it was found +func (a WithAnyAdditional2) Get(fieldName string) (value interface{}, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for WithAnyAdditional2 +func (a *WithAnyAdditional2) Set(fieldName string, value interface{}) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]interface{}) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for WithAnyAdditional2 to handle AdditionalProperties +func (a *WithAnyAdditional2) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["fieldA"]; found { + err = json.Unmarshal(raw, &a.FieldA) + if err != nil { + return fmt.Errorf("error reading 'fieldA': %w", err) + } + delete(object, "fieldA") + } + + if raw, found := object["fieldB"]; found { + err = json.Unmarshal(raw, &a.FieldB) + if err != nil { + return fmt.Errorf("error reading 'fieldB': %w", err) + } + delete(object, "fieldB") + } + + if len(object) != 0 { + a.AdditionalProperties = make(map[string]interface{}) + for fieldName, fieldBuf := range object { + var fieldVal interface{} + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for WithAnyAdditional2 to handle AdditionalProperties +func (a WithAnyAdditional2) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + if a.FieldA != nil { + object["fieldA"], err = json.Marshal(a.FieldA) + if err != nil { + return nil, fmt.Errorf("error marshaling 'fieldA': %w", err) + } + } + + if a.FieldB != nil { + object["fieldB"], err = json.Marshal(a.FieldB) + if err != nil { + return nil, fmt.Errorf("error marshaling 'fieldB': %w", err) + } + } + + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) +} + +// Getter for additional properties for WithStringAdditional1. Returns the specified +// element and whether it was found +func (a WithStringAdditional1) Get(fieldName string) (value string, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for WithStringAdditional1 +func (a *WithStringAdditional1) Set(fieldName string, value string) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]string) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for WithStringAdditional1 to handle AdditionalProperties +func (a *WithStringAdditional1) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["field1"]; found { + err = json.Unmarshal(raw, &a.Field1) + if err != nil { + return fmt.Errorf("error reading 'field1': %w", err) + } + delete(object, "field1") + } + + if raw, found := object["field2"]; found { + err = json.Unmarshal(raw, &a.Field2) + if err != nil { + return fmt.Errorf("error reading 'field2': %w", err) + } + delete(object, "field2") + } + + if len(object) != 0 { + a.AdditionalProperties = make(map[string]string) + for fieldName, fieldBuf := range object { + var fieldVal string + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for WithStringAdditional1 to handle AdditionalProperties +func (a WithStringAdditional1) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + if a.Field1 != nil { + object["field1"], err = json.Marshal(a.Field1) + if err != nil { + return nil, fmt.Errorf("error marshaling 'field1': %w", err) + } + } + + if a.Field2 != nil { + object["field2"], err = json.Marshal(a.Field2) + if err != nil { + return nil, fmt.Errorf("error marshaling 'field2': %w", err) + } + } + + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) +} + +// Getter for additional properties for WithStringAdditional2. Returns the specified +// element and whether it was found +func (a WithStringAdditional2) Get(fieldName string) (value string, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for WithStringAdditional2 +func (a *WithStringAdditional2) Set(fieldName string, value string) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]string) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for WithStringAdditional2 to handle AdditionalProperties +func (a *WithStringAdditional2) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["fieldA"]; found { + err = json.Unmarshal(raw, &a.FieldA) + if err != nil { + return fmt.Errorf("error reading 'fieldA': %w", err) + } + delete(object, "fieldA") + } + + if raw, found := object["fieldB"]; found { + err = json.Unmarshal(raw, &a.FieldB) + if err != nil { + return fmt.Errorf("error reading 'fieldB': %w", err) + } + delete(object, "fieldB") + } + + if len(object) != 0 { + a.AdditionalProperties = make(map[string]string) + for fieldName, fieldBuf := range object { + var fieldVal string + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for WithStringAdditional2 to handle AdditionalProperties +func (a WithStringAdditional2) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + if a.FieldA != nil { + object["fieldA"], err = json.Marshal(a.FieldA) + if err != nil { + return nil, fmt.Errorf("error marshaling 'fieldA': %w", err) + } + } + + if a.FieldB != nil { + object["fieldB"], err = json.Marshal(a.FieldB) + if err != nil { + return nil, fmt.Errorf("error marshaling 'fieldB': %w", err) + } + } + + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) +} diff --git a/internal/test/issues/issue-1219/issue.yaml b/internal/test/issues/issue-1219/issue.yaml new file mode 100644 index 0000000000..0437423277 --- /dev/null +++ b/internal/test/issues/issue-1219/issue.yaml @@ -0,0 +1,136 @@ +openapi: "3.0.1" +components: + schemas: + WithAnyAdditional1: + type: object + properties: + field1: + type: integer + field2: + type: string + additionalProperties: true + WithAnyAdditional2: + type: object + properties: + fieldA: + type: integer + fieldB: + type: string + additionalProperties: true + WithStringAdditional1: + type: object + properties: + field1: + type: integer + field2: + type: string + additionalProperties: + type: string + WithStringAdditional2: + type: object + properties: + fieldA: + type: integer + fieldB: + type: string + additionalProperties: + type: string + WithoutAdditional1: + type: object + properties: + field1: + type: integer + field2: + type: string + additionalProperties: false + WithoutAdditional2: + type: object + properties: + fieldA: + type: integer + fieldB: + type: string + additionalProperties: false + DefaultAdditional1: + type: object + properties: + field1: + type: integer + field2: + type: string + DefaultAdditional2: + type: object + properties: + fieldA: + type: integer + fieldB: + type: string + + MergeWithoutWithout: + allOf: + - $ref: '#/components/schemas/WithoutAdditional1' + - $ref: '#/components/schemas/WithoutAdditional2' + MergeWithoutWithString: + allOf: + - $ref: '#/components/schemas/WithoutAdditional1' + - $ref: '#/components/schemas/WithStringAdditional2' + MergeWithoutWithAny: + allOf: + - $ref: '#/components/schemas/WithoutAdditional1' + - $ref: '#/components/schemas/WithAnyAdditional2' + MergeWithoutDefault: + allOf: + - $ref: '#/components/schemas/WithoutAdditional1' + - $ref: '#/components/schemas/DefaultAdditional2' + + MergeWithStringWithout: + allOf: + - $ref: '#/components/schemas/WithStringAdditional1' + - $ref: '#/components/schemas/WithoutAdditional2' + # Cannot merge this + # MergeWithStringWithString: + # allOf: + # - $ref: '#/components/schemas/WithStringAdditional1' + # - $ref: '#/components/schemas/WithStringAdditional2' + MergeWithStringWithAny: + allOf: + - $ref: '#/components/schemas/WithStringAdditional1' + - $ref: '#/components/schemas/WithAnyAdditional2' + MergeWithStringDefault: + allOf: + - $ref: '#/components/schemas/WithStringAdditional1' + - $ref: '#/components/schemas/DefaultAdditional2' + + MergeWithAnyWithout: + allOf: + - $ref: '#/components/schemas/WithAnyAdditional1' + - $ref: '#/components/schemas/WithoutAdditional2' + MergeWithAnyWithString: + allOf: + - $ref: '#/components/schemas/WithAnyAdditional1' + - $ref: '#/components/schemas/WithStringAdditional2' + MergeWithAnyWithAny: + allOf: + - $ref: '#/components/schemas/WithAnyAdditional1' + - $ref: '#/components/schemas/WithAnyAdditional2' + MergeWithAnyDefault: + allOf: + - $ref: '#/components/schemas/WithAnyAdditional1' + - $ref: '#/components/schemas/DefaultAdditional2' + + MergeDefaultWithout: + allOf: + - $ref: '#/components/schemas/DefaultAdditional1' + - $ref: '#/components/schemas/WithoutAdditional2' + MergeDefaultWithString: + allOf: + - $ref: '#/components/schemas/DefaultAdditional1' + - $ref: '#/components/schemas/WithStringAdditional2' + MergeDefaultWithAny: + allOf: + - $ref: '#/components/schemas/DefaultAdditional1' + - $ref: '#/components/schemas/WithAnyAdditional2' + MergeDefaultDefault: + allOf: + - $ref: '#/components/schemas/DefaultAdditional1' + - $ref: '#/components/schemas/DefaultAdditional2' diff --git a/internal/test/issues/issue-1219/issue_test.go b/internal/test/issues/issue-1219/issue_test.go new file mode 100644 index 0000000000..aebd0d1afd --- /dev/null +++ b/internal/test/issues/issue-1219/issue_test.go @@ -0,0 +1,85 @@ +package issue1219_test + +import ( + "reflect" + "testing" + + issue1219 "github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-1219" + "github.com/stretchr/testify/assert" +) + +// Test treatment additionalProperties in mergeOpenapiSchemas() +func TestIssue1219(t *testing.T) { + var exist bool + // In the current oapi-codegen, additionalProperties is treated as `false’ unlike the openapi specification + // when it is not specified, so the case where additionalProperties is unspecified and + // the case where it is explicitly `true’ are treated separately. + + // When properties "additionalProperties" in both schemas are explicitly "true", + // the property "additionalProperties" in merged schema must be "true". + assert.IsType(t, map[string]interface{}{}, issue1219.MergeWithAnyWithAny{}.AdditionalProperties) + // Old behavior: generate fail + + // When property "additionalProperties" in one schema is explicitly "true" and + // property "additionalProperties" in another schema specifies sub-schema, + // the property "additionalProperties" in merged schema must use specified sub-schema in later source schema. + assert.IsType(t, map[string]string{}, issue1219.MergeWithAnyWithString{}.AdditionalProperties) + assert.IsType(t, map[string]string{}, issue1219.MergeWithStringWithAny{}.AdditionalProperties) + // Old behavior: generate fail + + // When property "additionalProperties" in one schema is explicitly "true" and + // property "additionalProperties" in another schema is not specified, + // the property "additionalProperties" in merged schema must be "true". + // This is because properties "additionalProperties" in both schemas are treated as "true" in the openapi specification. + assert.IsType(t, map[string]interface{}{}, issue1219.MergeWithAnyDefault{}.AdditionalProperties) + assert.IsType(t, map[string]interface{}{}, issue1219.MergeDefaultWithAny{}.AdditionalProperties) + // Old behavior: additionalProperties is treated as unspecified + + // When property "additionalProperties" in one schema is explicitly "true" and + // property "additionalProperties" in another schema is "false", + // the property "additionalProperties" in merged schema must be "false". + _, exist = reflect.TypeOf(issue1219.MergeWithAnyWithout{}).FieldByName("AdditionalProperties") + assert.False(t, exist) + _, exist = reflect.TypeOf(issue1219.MergeWithoutWithAny{}).FieldByName("AdditionalProperties") + assert.False(t, exist) + // Old behavior: additionalProperties is treated as unspecified + + // When properties "additionalProperties" in both schemas specify sub-schema, + // sub-schemas must be merged. + // But this is not yet implemented. + // issue1219.MergeWithStringWithString{} + + // When property "additionalProperties" in one schema specifies sub-schema and + // property "additionalProperties" in another schema is not specified, + // the property "additionalProperties" in merged schema must use specified sub-schema. + assert.IsType(t, map[string]string{}, issue1219.MergeWithStringDefault{}.AdditionalProperties) + assert.IsType(t, map[string]string{}, issue1219.MergeDefaultWithString{}.AdditionalProperties) + + // When property "additionalProperties" in one schema specifies sub-schema and + // property "additionalProperties" in another schema is "false", + // the property "additionalProperties" in merged schema must be "false". + _, exist = reflect.TypeOf(issue1219.MergeWithStringWithout{}).FieldByName("AdditionalProperties") + assert.False(t, exist) + _, exist = reflect.TypeOf(issue1219.MergeWithoutWithString{}).FieldByName("AdditionalProperties") + assert.False(t, exist) + // Old behavior: additionalProperties use specified sub-schema. + + // When properties "additionalProperties" in both schemas are not specified, + // the property "additionalProperties" in merged schema must be "true" in the openapi specification. + // But to avoid compatibility issue, property "additionalProperties" in merged schema is treated as unspecified. + _, exist = reflect.TypeOf(issue1219.MergeDefaultDefault{}).FieldByName("AdditionalProperties") + assert.False(t, exist) + + // When property "additionalProperties" in one schema is not specified and + // property "additionalProperties" in another schema is "false", + // the property "additionalProperties" in merged schema must be "false". + _, exist = reflect.TypeOf(issue1219.MergeDefaultWithout{}).FieldByName("AdditionalProperties") + assert.False(t, exist) + _, exist = reflect.TypeOf(issue1219.MergeWithoutDefault{}).FieldByName("AdditionalProperties") + assert.False(t, exist) + + // When properties "additionalProperties" in both schemas are "false", + // the property "additionalProperties" in merged schema must be "false". + _, exist = reflect.TypeOf(issue1219.MergeWithoutWithout{}).FieldByName("AdditionalProperties") + assert.False(t, exist) +} diff --git a/internal/test/issues/issue-1298/config.yaml b/internal/test/issues/issue-1298/config.yaml new file mode 100644 index 0000000000..76ef6715e7 --- /dev/null +++ b/internal/test/issues/issue-1298/config.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: issue1298 +generate: + models: true + client: true + gin-server: true + strict-server: true +output: issue1298.gen.go diff --git a/internal/test/issues/issue-1298/doc.go b/internal/test/issues/issue-1298/doc.go new file mode 100644 index 0000000000..b5e5ee95c9 --- /dev/null +++ b/internal/test/issues/issue-1298/doc.go @@ -0,0 +1,3 @@ +package issue1298 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml issue1298.yaml diff --git a/internal/test/issues/issue-1298/issue1298.gen.go b/internal/test/issues/issue-1298/issue1298.gen.go new file mode 100644 index 0000000000..2e5a48dc57 --- /dev/null +++ b/internal/test/issues/issue-1298/issue1298.gen.go @@ -0,0 +1,404 @@ +// Package issue1298 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue1298 + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/gin-gonic/gin" + strictgin "github.com/oapi-codegen/runtime/strictmiddleware/gin" +) + +// Test defines model for Test. +type Test struct { + Field1 string `json:"field1"` + Field2 string `json:"field2"` +} + +// TestApplicationTestPlusJSONRequestBody defines body for Test for application/test+json ContentType. +type TestApplicationTestPlusJSONRequestBody = Test + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // TestWithBody request with any body + TestWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + TestWithApplicationTestPlusJSONBody(ctx context.Context, body TestApplicationTestPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) TestWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewTestRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) TestWithApplicationTestPlusJSONBody(ctx context.Context, body TestApplicationTestPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewTestRequestWithApplicationTestPlusJSONBody(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewTestRequestWithApplicationTestPlusJSONBody calls the generic Test builder with application/test+json body +func NewTestRequestWithApplicationTestPlusJSONBody(server string, body TestApplicationTestPlusJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewTestRequestWithBody(server, "application/test+json", bodyReader) +} + +// NewTestRequestWithBody generates requests for Test with any type of body +func NewTestRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/test") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // TestWithBodyWithResponse request with any body + TestWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TestResponse, error) + + TestWithApplicationTestPlusJSONBodyWithResponse(ctx context.Context, body TestApplicationTestPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*TestResponse, error) +} + +type TestResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r TestResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r TestResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// TestWithBodyWithResponse request with arbitrary body returning *TestResponse +func (c *ClientWithResponses) TestWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TestResponse, error) { + rsp, err := c.TestWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseTestResponse(rsp) +} + +func (c *ClientWithResponses) TestWithApplicationTestPlusJSONBodyWithResponse(ctx context.Context, body TestApplicationTestPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*TestResponse, error) { + rsp, err := c.TestWithApplicationTestPlusJSONBody(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseTestResponse(rsp) +} + +// ParseTestResponse parses an HTTP response from a TestWithResponse call +func ParseTestResponse(rsp *http.Response) (*TestResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &TestResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /test) + Test(c *gin.Context) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) +} + +type MiddlewareFunc func(c *gin.Context) + +// Test operation middleware +func (siw *ServerInterfaceWrapper) Test(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.Test(c) +} + +// GinServerOptions provides options for the Gin server. +type GinServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router gin.IRouter, si ServerInterface) { + RegisterHandlersWithOptions(router, si, GinServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options GinServerOptions) { + errorHandler := options.ErrorHandler + if errorHandler == nil { + errorHandler = func(c *gin.Context, err error, statusCode int) { + c.JSON(statusCode, gin.H{"msg": err.Error()}) + } + } + + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandler: errorHandler, + } + + router.GET(options.BaseURL+"/test", wrapper.Test) +} + +type TestRequestObject struct { + Body *TestApplicationTestPlusJSONRequestBody +} + +type TestResponseObject interface { + VisitTestResponse(w http.ResponseWriter) error +} + +type Test204Response struct { +} + +func (response Test204Response) VisitTestResponse(w http.ResponseWriter) error { + w.WriteHeader(204) + return nil +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (GET /test) + Test(ctx context.Context, request TestRequestObject) (TestResponseObject, error) +} + +type StrictHandlerFunc = strictgin.StrictGinHandlerFunc +type StrictMiddlewareFunc = strictgin.StrictGinMiddlewareFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +// Test operation middleware +func (sh *strictHandler) Test(ctx *gin.Context) { + var request TestRequestObject + + var body TestApplicationTestPlusJSONRequestBody + if err := ctx.ShouldBindJSON(&body); err != nil { + if !errors.Is(err, io.EOF) { + ctx.Status(http.StatusBadRequest) + ctx.Error(err) + return + } + } else { + request.Body = &body + } + + handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { + return sh.ssi.Test(ctx, request.(TestRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "Test") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.Error(err) + ctx.Status(http.StatusInternalServerError) + } else if validResponse, ok := response.(TestResponseObject); ok { + if err := validResponse.VisitTestResponse(ctx.Writer); err != nil { + ctx.Error(err) + } + } else if response != nil { + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) + } +} diff --git a/internal/test/issues/issue-1298/issue1298.yaml b/internal/test/issues/issue-1298/issue1298.yaml new file mode 100644 index 0000000000..889d0c2b17 --- /dev/null +++ b/internal/test/issues/issue-1298/issue1298.yaml @@ -0,0 +1,25 @@ +openapi: "3.0.1" +components: + schemas: + Test: + type: object + properties: + field1: + type: string + field2: + type: string + required: + - field1 + - field2 +paths: + /test: + get: + operationId: test + requestBody: + content: + application/test+json: + schema: + $ref: "#/components/schemas/Test" + responses: + 204: + description: good diff --git a/internal/test/issues/issue-1298/issue1298_test.go b/internal/test/issues/issue-1298/issue1298_test.go new file mode 100644 index 0000000000..0d627f10f5 --- /dev/null +++ b/internal/test/issues/issue-1298/issue1298_test.go @@ -0,0 +1,44 @@ +package issue1298_test + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + issue1298 "github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-1298" + "github.com/stretchr/testify/assert" +) + +type testStrictServerInterface struct { + t *testing.T +} + +// (GET /test) +func (s *testStrictServerInterface) Test(ctx context.Context, request issue1298.TestRequestObject) (issue1298.TestResponseObject, error) { + assert.Equal(s.t, "test1", request.Body.Field1) + assert.Equal(s.t, "test2", request.Body.Field2) + return issue1298.Test204Response{}, nil +} + +func TestIssue1298(t *testing.T) { + g := gin.Default() + issue1298.RegisterHandlersWithOptions(g, + issue1298.NewStrictHandler(&testStrictServerInterface{t: t}, nil), + issue1298.GinServerOptions{}) + ts := httptest.NewServer(g) + defer ts.Close() + + c, err := issue1298.NewClientWithResponses(ts.URL) + assert.NoError(t, err) + res, err := c.TestWithApplicationTestPlusJSONBodyWithResponse( + context.TODO(), + issue1298.Test{ + Field1: "test1", + Field2: "test2", + }, + ) + assert.NoError(t, err) + assert.Equal(t, http.StatusNoContent, res.StatusCode()) +} diff --git a/internal/test/issues/issue-1378/bionicle.yaml b/internal/test/issues/issue-1378/bionicle.yaml new file mode 100644 index 0000000000..0013d121a5 --- /dev/null +++ b/internal/test/issues/issue-1378/bionicle.yaml @@ -0,0 +1,43 @@ +openapi: 3.0.1 + +info: + title: Test + description: Test + version: 1.0.0 + +paths: + /bionicle/{name}: + get: + parameters: + - $ref: "#/components/parameters/bionicleName" + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Bionicle' + '400': + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Bionicle' + +components: + parameters: + bionicleName: + name: name + in: path + description: Name of the character + required: true + schema: + type: string + + schemas: + Bionicle: + type: object + properties: + name: + type: string + required: + - name diff --git a/internal/test/issues/issue-1378/bionicle/bionicle.gen.go b/internal/test/issues/issue-1378/bionicle/bionicle.gen.go new file mode 100644 index 0000000000..863c5fd380 --- /dev/null +++ b/internal/test/issues/issue-1378/bionicle/bionicle.gen.go @@ -0,0 +1,369 @@ +// Package bionicle provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package bionicle + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/gorilla/mux" + externalRef0 "github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-1378/common" + "github.com/oapi-codegen/runtime" + strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp" +) + +// Bionicle defines model for Bionicle. +type Bionicle struct { + Name string `json:"name"` +} + +// BionicleName defines model for bionicleName. +type BionicleName = string + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /bionicle/{name}) + GetBionicleName(w http.ResponseWriter, r *http.Request, name BionicleName) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetBionicleName operation middleware +func (siw *ServerInterfaceWrapper) GetBionicleName(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "name" ------------- + var name BionicleName + + err = runtime.BindStyledParameterWithOptions("simple", "name", mux.Vars(r)["name"], &name, runtime.BindStyledParameterOptions{Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "name", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetBionicleName(w, r, name) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{}) +} + +type GorillaServerOptions struct { + BaseURL string + BaseRouter *mux.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r *mux.Router) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r *mux.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options GorillaServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = mux.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.HandleFunc(options.BaseURL+"/bionicle/{name}", wrapper.GetBionicleName).Methods("GET") + + return r +} + +type GetBionicleNameRequestObject struct { + Name BionicleName `json:"name"` +} + +type GetBionicleNameResponseObject interface { + VisitGetBionicleNameResponse(w http.ResponseWriter) error +} + +type GetBionicleName200JSONResponse Bionicle + +func (response GetBionicleName200JSONResponse) VisitGetBionicleNameResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetBionicleName400JSONResponse struct { + union json.RawMessage +} + +func (response GetBionicleName400JSONResponse) VisitGetBionicleNameResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response.union) +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (GET /bionicle/{name}) + GetBionicleName(ctx context.Context, request GetBionicleNameRequestObject) (GetBionicleNameResponseObject, error) +} + +type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc +type StrictMiddlewareFunc = strictnethttp.StrictHTTPMiddlewareFunc + +type StrictHTTPServerOptions struct { + RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictHTTPServerOptions{ + RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + }, + ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusInternalServerError) + }, + }} +} + +func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictHTTPServerOptions) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: options} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc + options StrictHTTPServerOptions +} + +// GetBionicleName operation middleware +func (sh *strictHandler) GetBionicleName(w http.ResponseWriter, r *http.Request, name BionicleName) { + var request GetBionicleNameRequestObject + + request.Name = name + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetBionicleName(ctx, request.(GetBionicleNameRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetBionicleName") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetBionicleNameResponseObject); ok { + if err := validResponse.VisitGetBionicleNameResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/5SRMU8zMQyG/0rl7xujyxWYbuyCWGBhqzqkqa+XqpcEx0VCp/x35FyPXikMZLEcO3n8", + "+h3Ahj4Gj54TNANEQ6ZHRirZ1gXv7BGfTY+S7zBZcpFd8NCA3C5Cu+AOF7YzZCwjgQInxWi4AwW+vByD", + "AsK3kyPcQcN0QgXJdtgb+Zk/ovQlJuf3kHOeimWO1XmOMiGFiMQOS8WfJ/v+fs5aj10bNXWF7QEtjxTn", + "23Ar7RUTgwJ2LNApfUdKY31Z1VUNWUGI6E100MB9VVdLUEV4GU1P29OD8LPc7ZEliAIjqKcdNPCIvJov", + "Wl25sB7gP2ELDfzTF6/0pUVfuZQ3Ij3F4NO4obu6lmCDZ/SFbmI8Olv4+pBEzzBz4ifY2Qr95UMuu3v4", + "49fB40v7q6JbyCZP5zMAAP//9Jg3p6cCAAA=", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + for rawPath, rawFunc := range externalRef0.PathToRawSpec(path.Join(path.Dir(pathToFile), "common.yaml")) { + if _, ok := res[rawPath]; ok { + // it is not possible to compare functions in golang, so always overwrite the old value + } + res[rawPath] = rawFunc + } + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/issues/issue-1378/bionicle/config.yaml b/internal/test/issues/issue-1378/bionicle/config.yaml new file mode 100644 index 0000000000..4f81605dc4 --- /dev/null +++ b/internal/test/issues/issue-1378/bionicle/config.yaml @@ -0,0 +1,10 @@ +# yaml-language-server: $schema=../../../../../configuration-schema.json +package: bionicle +generate: + gorilla-server: true + embedded-spec: true + strict-server: true + models: true +import-mapping: + common.yaml: github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-1378/common +output: bionicle.gen.go diff --git a/internal/test/issues/issue-1378/bionicle/generate.go b/internal/test/issues/issue-1378/bionicle/generate.go new file mode 100644 index 0000000000..26b3661696 --- /dev/null +++ b/internal/test/issues/issue-1378/bionicle/generate.go @@ -0,0 +1,3 @@ +package bionicle + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../bionicle.yaml diff --git a/internal/test/issues/issue-1378/common.yaml b/internal/test/issues/issue-1378/common.yaml new file mode 100644 index 0000000000..8aac6cdf4c --- /dev/null +++ b/internal/test/issues/issue-1378/common.yaml @@ -0,0 +1,16 @@ +openapi: 3.0.1 + +info: + title: Test + description: Test + version: 1.0.0 + +components: + schemas: + ErrTracingIdNotSent: + type: object + properties: + code: + type: string + required: + - code diff --git a/internal/test/issues/issue-1378/common/common.gen.go b/internal/test/issues/issue-1378/common/common.gen.go new file mode 100644 index 0000000000..d1747783e0 --- /dev/null +++ b/internal/test/issues/issue-1378/common/common.gen.go @@ -0,0 +1,262 @@ +// Package common provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package common + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/gorilla/mux" + strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp" +) + +// ErrTracingIdNotSent defines model for ErrTracingIdNotSent. +type ErrTracingIdNotSent struct { + Code string `json:"code"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{}) +} + +type GorillaServerOptions struct { + BaseURL string + BaseRouter *mux.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r *mux.Router) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r *mux.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options GorillaServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = mux.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + + return r +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { +} + +type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc +type StrictMiddlewareFunc = strictnethttp.StrictHTTPMiddlewareFunc + +type StrictHTTPServerOptions struct { + RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictHTTPServerOptions{ + RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + }, + ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusInternalServerError) + }, + }} +} + +func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictHTTPServerOptions) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: options} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc + options StrictHTTPServerOptions +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/0TMP08DMQwF8O/y5uh0FVt2BhYWuiGGkJjW6Gobx0VCVb47Sk+IyX/eT++GqhdTIYmO", + "fEOvZ7qU+/rofvRSWU5P7VnjhSTm21yNPJjuqGqjOePHCBk9nOWEMRKcvq7s1JBfd/WW/pS+f1INjMlY", + "PnQWNOrV2YJVkHGkHkgIjo3+z2/yvueHZV1WjAQ1kmKMjIdlXQ5IsBLnjizXbRu/AQAA//9/cprb3gAA", + "AA==", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/issues/issue-1378/common/config.yaml b/internal/test/issues/issue-1378/common/config.yaml new file mode 100644 index 0000000000..2d2960cdcf --- /dev/null +++ b/internal/test/issues/issue-1378/common/config.yaml @@ -0,0 +1,10 @@ +# yaml-language-server: $schema=../../../../../configuration-schema.json +package: common +generate: + gorilla-server: true + embedded-spec: true + strict-server: true + models: true +output-options: + skip-prune: true +output: common.gen.go diff --git a/internal/test/issues/issue-1378/common/generate.go b/internal/test/issues/issue-1378/common/generate.go new file mode 100644 index 0000000000..dbb23b05d1 --- /dev/null +++ b/internal/test/issues/issue-1378/common/generate.go @@ -0,0 +1,3 @@ +package common + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../common.yaml diff --git a/internal/test/issues/issue-1378/foo-service.yaml b/internal/test/issues/issue-1378/foo-service.yaml new file mode 100644 index 0000000000..ebd8447440 --- /dev/null +++ b/internal/test/issues/issue-1378/foo-service.yaml @@ -0,0 +1,10 @@ +openapi: 3.0.1 + +info: + title: Test + description: Test + version: 1.0.0 + +paths: + /bionicle/{name}: + $ref: "bionicle.yaml#/paths/~1bionicle~1{name}" diff --git a/internal/test/issues/issue-1378/fooservice/config.yaml b/internal/test/issues/issue-1378/fooservice/config.yaml new file mode 100644 index 0000000000..4690bd037e --- /dev/null +++ b/internal/test/issues/issue-1378/fooservice/config.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../../../../../configuration-schema.json +package: fooservice +generate: + gorilla-server: true + embedded-spec: true + strict-server: true + models: true +import-mapping: + common.yaml: github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-1378/common + bionicle.yaml: github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-1378/bionicle +output: fooservice.gen.go diff --git a/internal/test/issues/issue-1378/fooservice/fooservice.gen.go b/internal/test/issues/issue-1378/fooservice/fooservice.gen.go new file mode 100644 index 0000000000..d076409c14 --- /dev/null +++ b/internal/test/issues/issue-1378/fooservice/fooservice.gen.go @@ -0,0 +1,368 @@ +// Package fooservice provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package fooservice + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/gorilla/mux" + externalRef0 "github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-1378/bionicle" + externalRef1 "github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-1378/common" + "github.com/oapi-codegen/runtime" + strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /bionicle/{name}) + GetBionicleName(w http.ResponseWriter, r *http.Request, name externalRef0.BionicleName) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetBionicleName operation middleware +func (siw *ServerInterfaceWrapper) GetBionicleName(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "name" ------------- + var name externalRef0.BionicleName + + err = runtime.BindStyledParameterWithOptions("simple", "name", mux.Vars(r)["name"], &name, runtime.BindStyledParameterOptions{Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "name", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetBionicleName(w, r, name) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{}) +} + +type GorillaServerOptions struct { + BaseURL string + BaseRouter *mux.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r *mux.Router) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r *mux.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options GorillaServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = mux.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.HandleFunc(options.BaseURL+"/bionicle/{name}", wrapper.GetBionicleName).Methods("GET") + + return r +} + +type GetBionicleNameRequestObject struct { + Name externalRef0.BionicleName `json:"name"` +} + +type GetBionicleNameResponseObject interface { + VisitGetBionicleNameResponse(w http.ResponseWriter) error +} + +type GetBionicleName200JSONResponse externalRef0.Bionicle + +func (response GetBionicleName200JSONResponse) VisitGetBionicleNameResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetBionicleName400JSONResponse struct { + union json.RawMessage +} + +func (response GetBionicleName400JSONResponse) VisitGetBionicleNameResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response.union) +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (GET /bionicle/{name}) + GetBionicleName(ctx context.Context, request GetBionicleNameRequestObject) (GetBionicleNameResponseObject, error) +} + +type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc +type StrictMiddlewareFunc = strictnethttp.StrictHTTPMiddlewareFunc + +type StrictHTTPServerOptions struct { + RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictHTTPServerOptions{ + RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + }, + ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusInternalServerError) + }, + }} +} + +func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictHTTPServerOptions) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: options} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc + options StrictHTTPServerOptions +} + +// GetBionicleName operation middleware +func (sh *strictHandler) GetBionicleName(w http.ResponseWriter, r *http.Request, name externalRef0.BionicleName) { + var request GetBionicleNameRequestObject + + request.Name = name + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetBionicleName(ctx, request.(GetBionicleNameRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetBionicleName") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetBionicleNameResponseObject); ok { + if err := validResponse.VisitGetBionicleNameResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/5RRQU/zMAz9K5O/7xg1HXDqcRfEBS7cpgllmbtmWpPgeEioyn9HTldtYwOJXpzEr37P", + "7w1gQx+DR88JmgGiIdMjI5Xb2gXv7B7fpsOz6VEaG0yWXGQXPDQgr7PQzrjDme0MGctIoMBJMxruQIEv", + "f45FAeH7wRFuoGE6oIJkO+yNTObPKLjE5PwWcs5T81LQ4ngomilEJHZYIP4o8fugc9LliFqpCRXWO7Q8", + "0jnfhusdXzExKGDHQjpdP5DS2J9XdVVDVhAiehMdNHBf1dUcVHGgSNOTfD0If5a3LbIU2cAI1dMGGnhE", + "Xpw7ri5yWQ7wn7CFBv7pU3r6BNG3c8sr8SDF4NNo1V1dS7HBM/oiw8S4d7YI0bskiw1n2dxiPYajr5PJ", + "xc2HP3IEjy/tjzv+wrbK0/cVAAD//37MjEPUAgAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + for rawPath, rawFunc := range externalRef0.PathToRawSpec(path.Join(path.Dir(pathToFile), "bionicle.yaml")) { + if _, ok := res[rawPath]; ok { + // it is not possible to compare functions in golang, so always overwrite the old value + } + res[rawPath] = rawFunc + } + for rawPath, rawFunc := range externalRef1.PathToRawSpec(path.Join(path.Dir(pathToFile), "common.yaml")) { + if _, ok := res[rawPath]; ok { + // it is not possible to compare functions in golang, so always overwrite the old value + } + res[rawPath] = rawFunc + } + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/issues/issue-1378/fooservice/generate.go b/internal/test/issues/issue-1378/fooservice/generate.go new file mode 100644 index 0000000000..2419719dbf --- /dev/null +++ b/internal/test/issues/issue-1378/fooservice/generate.go @@ -0,0 +1,3 @@ +package fooservice + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../foo-service.yaml diff --git a/internal/test/issues/issue-1397/config.yaml b/internal/test/issues/issue-1397/config.yaml new file mode 100644 index 0000000000..d4a1284eeb --- /dev/null +++ b/internal/test/issues/issue-1397/config.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: issue1397 +generate: + echo-server: true + client: true + models: true + embedded-spec: true +output: issue1397.gen.go diff --git a/internal/test/issues/issue-1397/doc.go b/internal/test/issues/issue-1397/doc.go new file mode 100644 index 0000000000..e5964fe298 --- /dev/null +++ b/internal/test/issues/issue-1397/doc.go @@ -0,0 +1,3 @@ +package issue1397 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/issues/issue-1397/issue1397.gen.go b/internal/test/issues/issue-1397/issue1397.gen.go new file mode 100644 index 0000000000..11bc883db2 --- /dev/null +++ b/internal/test/issues/issue-1397/issue1397.gen.go @@ -0,0 +1,452 @@ +// Package issue1397 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue1397 + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/labstack/echo/v4" +) + +// Defines values for TestField1. +const ( + Option1 TestField1 = "option1" + Option2 TestField1 = "option2" +) + +// Valid indicates whether the value is a known member of the TestField1 enum. +func (e TestField1) Valid() bool { + switch e { + case Option1: + return true + case Option2: + return true + default: + return false + } +} + +// Test defines model for Test. +type Test = MyTestRequest + +// TestField1 defines model for Test.Field1. +type TestField1 string + +// MyTestRequestNestedField A nested object with allocated name +type MyTestRequestNestedField struct { + Field1 bool `json:"field1"` + Field2 string `json:"field2"` +} + +// MyTestRequest defines model for . +type MyTestRequest struct { + // Field1 A array of enum values + Field1 *[]TestField1 `json:"field1,omitempty"` + + // Field2 A nested object with allocated name + Field2 *MyTestRequestNestedField `json:"field2,omitempty"` + + // Field3 A nested object without allocated name + Field3 *struct { + Field1 bool `json:"field1"` + Field2 string `json:"field2"` + } `json:"field3,omitempty"` +} + +// TestApplicationTestPlusJSONRequestBody defines body for Test for application/test+json ContentType. +type TestApplicationTestPlusJSONRequestBody = Test + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // TestWithBody request with any body + TestWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + TestWithApplicationTestPlusJSONBody(ctx context.Context, body TestApplicationTestPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) TestWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewTestRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) TestWithApplicationTestPlusJSONBody(ctx context.Context, body TestApplicationTestPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewTestRequestWithApplicationTestPlusJSONBody(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewTestRequestWithApplicationTestPlusJSONBody calls the generic Test builder with application/test+json body +func NewTestRequestWithApplicationTestPlusJSONBody(server string, body TestApplicationTestPlusJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewTestRequestWithBody(server, "application/test+json", bodyReader) +} + +// NewTestRequestWithBody generates requests for Test with any type of body +func NewTestRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/test") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // TestWithBodyWithResponse request with any body + TestWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TestResponse, error) + + TestWithApplicationTestPlusJSONBodyWithResponse(ctx context.Context, body TestApplicationTestPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*TestResponse, error) +} + +type TestResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r TestResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r TestResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// TestWithBodyWithResponse request with arbitrary body returning *TestResponse +func (c *ClientWithResponses) TestWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TestResponse, error) { + rsp, err := c.TestWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseTestResponse(rsp) +} + +func (c *ClientWithResponses) TestWithApplicationTestPlusJSONBodyWithResponse(ctx context.Context, body TestApplicationTestPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*TestResponse, error) { + rsp, err := c.TestWithApplicationTestPlusJSONBody(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseTestResponse(rsp) +} + +// ParseTestResponse parses an HTTP response from a TestWithResponse call +func ParseTestResponse(rsp *http.Response) (*TestResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &TestResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /test) + Test(ctx echo.Context) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// Test converts echo context to params. +func (w *ServerInterfaceWrapper) Test(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.Test(ctx) + return err +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.GET(baseURL+"/test", wrapper.Test) + +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/8xTO2/bMBD+K8K1W2XLj3bh1g4FPLRD4C3IQEsnmQbFo8lTYsHQfw+OUmzkBSRbJh2O", + "9/F76HiGklpPDh1HUGeI5R5bncotRpavD+QxsMHUrQ3aailVhbEMxrMhBwp+ZzoE3WdUZ+i6NrvXtsMI", + "ORjGNiGlDeoWKEGWkE/VCu5y4N4jKIgcjGtgyKHVp82I/HU5TQxymESs3hLhMDJWGe0OWHL2YHifaWup", + "1NJ1ukXI3zU0seyILGr3nOeFviGHgMfOBKzE0nTHBXA1NAqBHE6zhmbSnCUVCv71EvANHjuM/D/J/ivo", + "C+/6Y/6o4y9hUQCfMw2DYIyrCZTrrJWFQKe9AQXr+WIubF7zPnkoeFrHBtNH/GmJZVOBGnd1FIyR/1DV", + "y0xJjtGlce29NWUCpJt+HKIk+rTwUn0PWIOCb8X1RRTTcyi2k9wUSvTk4pjsavHz9V9qiKo0PAyPAQAA", + "///99gukXwMAAA==", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/issues/issue-1397/spec.yaml b/internal/test/issues/issue-1397/spec.yaml new file mode 100644 index 0000000000..93cc609edf --- /dev/null +++ b/internal/test/issues/issue-1397/spec.yaml @@ -0,0 +1,52 @@ +openapi: "3.0.1" +components: + schemas: + Test: + type: object + properties: + field1: + description: A array of enum values + items: + enum: + - option1 + - option2 + type: string + maxItems: 5 + minItems: 0 + type: array + field2: + description: A nested object with allocated name + properties: + field1: + type: boolean + field2: + type: string + required: + - field1 + - field2 + type: object + x-go-type-name: MyTestRequestNestedField + field3: + description: A nested object without allocated name + properties: + field1: + type: boolean + field2: + type: string + required: + - field1 + - field2 + type: object + x-go-type-name: MyTestRequest +paths: + /test: + get: + operationId: test + requestBody: + content: + application/test+json: + schema: + $ref: "#/components/schemas/Test" + responses: + 204: + description: good diff --git a/internal/test/issues/issue-1529/strict-echo/config.yaml b/internal/test/issues/issue-1529/strict-echo/config.yaml new file mode 100644 index 0000000000..56abc0bee7 --- /dev/null +++ b/internal/test/issues/issue-1529/strict-echo/config.yaml @@ -0,0 +1,9 @@ +# yaml-language-server: $schema=../../../../../configuration-schema.json +package: issue1529 +generate: + client: true + models: true + embedded-spec: true + echo-server: true + strict-server: true +output: issue1529.gen.go diff --git a/internal/test/issues/issue-1529/strict-echo/doc.go b/internal/test/issues/issue-1529/strict-echo/doc.go new file mode 100644 index 0000000000..4bf78249fa --- /dev/null +++ b/internal/test/issues/issue-1529/strict-echo/doc.go @@ -0,0 +1,3 @@ +package issue1529 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/issues/issue-1529/strict-echo/issue1529.gen.go b/internal/test/issues/issue-1529/strict-echo/issue1529.gen.go new file mode 100644 index 0000000000..cd5260fae4 --- /dev/null +++ b/internal/test/issues/issue-1529/strict-echo/issue1529.gen.go @@ -0,0 +1,471 @@ +// Package issue1529 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue1529 + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/labstack/echo/v4" + strictecho "github.com/oapi-codegen/runtime/strictmiddleware/echo" +) + +// Test defines model for Test. +type Test = map[string]interface{} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // Test request + Test(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) Test(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewTestRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewTestRequest generates requests for Test +func NewTestRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/test") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // TestWithResponse request + TestWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*TestResponse, error) +} + +type TestResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Test + ApplicationjsonProfileBar200 *Test + ApplicationjsonProfileFoo200 *Test +} + +// Status returns HTTPResponse.Status +func (r TestResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r TestResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// TestWithResponse request returning *TestResponse +func (c *ClientWithResponses) TestWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*TestResponse, error) { + rsp, err := c.Test(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseTestResponse(rsp) +} + +// ParseTestResponse parses an HTTP response from a TestWithResponse call +func ParseTestResponse(rsp *http.Response) (*TestResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &TestResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case rsp.Header.Get("Content-Type") == "application/json" && rsp.StatusCode == 200: + var dest Test + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case rsp.Header.Get("Content-Type") == "application/json; profile=\"Bar\"" && rsp.StatusCode == 200: + var dest Test + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.ApplicationjsonProfileBar200 = &dest + + case rsp.Header.Get("Content-Type") == "application/json; profile=\"Foo\"" && rsp.StatusCode == 200: + var dest Test + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.ApplicationjsonProfileFoo200 = &dest + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /test) + Test(ctx echo.Context) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// Test converts echo context to params. +func (w *ServerInterfaceWrapper) Test(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.Test(ctx) + return err +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.GET(baseURL+"/test", wrapper.Test) + +} + +type TestRequestObject struct { +} + +type TestResponseObject interface { + VisitTestResponse(w http.ResponseWriter) error +} + +type Test200JSONResponse Test + +func (response Test200JSONResponse) VisitTestResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type Test200ApplicationJSONProfileBarResponse Test + +func (response Test200ApplicationJSONProfileBarResponse) VisitTestResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json; profile=\"Bar\"") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type Test200ApplicationJSONProfileFooResponse Test + +func (response Test200ApplicationJSONProfileFooResponse) VisitTestResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json; profile=\"Foo\"") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (GET /test) + Test(ctx context.Context, request TestRequestObject) (TestResponseObject, error) +} + +type StrictHandlerFunc = strictecho.StrictEchoHandlerFunc +type StrictMiddlewareFunc = strictecho.StrictEchoMiddlewareFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +// Test operation middleware +func (sh *strictHandler) Test(ctx echo.Context) error { + var request TestRequestObject + + handler := func(ctx echo.Context, request interface{}) (interface{}, error) { + return sh.ssi.Test(ctx.Request().Context(), request.(TestRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "Test") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(TestResponseObject); ok { + return validResponse.VisitTestResponse(ctx.Response()) + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/6zPMUsEMRAF4L8iT8twWbWL2FgI9pbXxDjr5cjNDMlYyLL/XRIXFizFad4072NmQZKL", + "ChNbQ1jQ0okucayv1KynfSkhQN7OlAzrujpkngWBP0txECWOmhFwf5gOt3DQaKcheNuIDxohSjVaFn55", + "R/jxHSo1FW40GnfT1CMJG/HoRNWS02j5cxPej+zbTaUZAdd+/8JvL/jh92t/Ew9XWmXOhR6PeIr1iH82", + "n0X+YG7zHQAA//9fnz6pkQEAAA==", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/issues/issue-1529/strict-echo/spec.yaml b/internal/test/issues/issue-1529/strict-echo/spec.yaml new file mode 100644 index 0000000000..ca7aae80a3 --- /dev/null +++ b/internal/test/issues/issue-1529/strict-echo/spec.yaml @@ -0,0 +1,21 @@ +openapi: "3.0.1" +components: + schemas: + Test: + type: object +paths: + /test: + get: + operationId: test + responses: + 200: + content: + application/json: + schema: + $ref: "#/components/schemas/Test" + application/json; profile="Foo": + schema: + $ref: "#/components/schemas/Test" + application/json; profile="Bar": + schema: + $ref: "#/components/schemas/Test" diff --git a/internal/test/issues/issue-1529/strict-fiber/config.yaml b/internal/test/issues/issue-1529/strict-fiber/config.yaml new file mode 100644 index 0000000000..e03a3f678b --- /dev/null +++ b/internal/test/issues/issue-1529/strict-fiber/config.yaml @@ -0,0 +1,9 @@ +# yaml-language-server: $schema=../../../../../configuration-schema.json +package: issue1529 +generate: + client: true + models: true + embedded-spec: true + fiber-server: true + strict-server: true +output: issue1529.gen.go diff --git a/internal/test/issues/issue-1529/strict-fiber/doc.go b/internal/test/issues/issue-1529/strict-fiber/doc.go new file mode 100644 index 0000000000..4bf78249fa --- /dev/null +++ b/internal/test/issues/issue-1529/strict-fiber/doc.go @@ -0,0 +1,3 @@ +package issue1529 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/issues/issue-1529/strict-fiber/issue1529.gen.go b/internal/test/issues/issue-1529/strict-fiber/issue1529.gen.go new file mode 100644 index 0000000000..e1324845b6 --- /dev/null +++ b/internal/test/issues/issue-1529/strict-fiber/issue1529.gen.go @@ -0,0 +1,465 @@ +// Package issue1529 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue1529 + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/gofiber/fiber/v2" +) + +// Test defines model for Test. +type Test = map[string]interface{} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // Test request + Test(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) Test(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewTestRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewTestRequest generates requests for Test +func NewTestRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/test") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // TestWithResponse request + TestWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*TestResponse, error) +} + +type TestResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Test + ApplicationjsonProfileBar200 *Test + ApplicationjsonProfileFoo200 *Test +} + +// Status returns HTTPResponse.Status +func (r TestResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r TestResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// TestWithResponse request returning *TestResponse +func (c *ClientWithResponses) TestWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*TestResponse, error) { + rsp, err := c.Test(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseTestResponse(rsp) +} + +// ParseTestResponse parses an HTTP response from a TestWithResponse call +func ParseTestResponse(rsp *http.Response) (*TestResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &TestResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case rsp.Header.Get("Content-Type") == "application/json" && rsp.StatusCode == 200: + var dest Test + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case rsp.Header.Get("Content-Type") == "application/json; profile=\"Bar\"" && rsp.StatusCode == 200: + var dest Test + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.ApplicationjsonProfileBar200 = &dest + + case rsp.Header.Get("Content-Type") == "application/json; profile=\"Foo\"" && rsp.StatusCode == 200: + var dest Test + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.ApplicationjsonProfileFoo200 = &dest + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /test) + Test(c *fiber.Ctx) error +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +type MiddlewareFunc fiber.Handler + +// Test operation middleware +func (siw *ServerInterfaceWrapper) Test(c *fiber.Ctx) error { + + return siw.Handler.Test(c) +} + +// FiberServerOptions provides options for the Fiber server. +type FiberServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router fiber.Router, si ServerInterface) { + RegisterHandlersWithOptions(router, si, FiberServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router fiber.Router, si ServerInterface, options FiberServerOptions) { + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + for _, m := range options.Middlewares { + router.Use(fiber.Handler(m)) + } + + router.Get(options.BaseURL+"/test", wrapper.Test) + +} + +type TestRequestObject struct { +} + +type TestResponseObject interface { + VisitTestResponse(ctx *fiber.Ctx) error +} + +type Test200JSONResponse Test + +func (response Test200JSONResponse) VisitTestResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response) +} + +type Test200ApplicationJSONProfileBarResponse Test + +func (response Test200ApplicationJSONProfileBarResponse) VisitTestResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json; profile=\"Bar\"") + ctx.Status(200) + + return ctx.JSON(&response) +} + +type Test200ApplicationJSONProfileFooResponse Test + +func (response Test200ApplicationJSONProfileFooResponse) VisitTestResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json; profile=\"Foo\"") + ctx.Status(200) + + return ctx.JSON(&response) +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (GET /test) + Test(ctx context.Context, request TestRequestObject) (TestResponseObject, error) +} + +type StrictHandlerFunc func(ctx *fiber.Ctx, args interface{}) (interface{}, error) + +type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +// Test operation middleware +func (sh *strictHandler) Test(ctx *fiber.Ctx) error { + var request TestRequestObject + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.Test(ctx.UserContext(), request.(TestRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "Test") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(TestResponseObject); ok { + if err := validResponse.VisitTestResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/6zPMUsEMRAF4L8iT8twWbWL2FgI9pbXxDjr5cjNDMlYyLL/XRIXFizFad4072NmQZKL", + "ChNbQ1jQ0okucayv1KynfSkhQN7OlAzrujpkngWBP0txECWOmhFwf5gOt3DQaKcheNuIDxohSjVaFn55", + "R/jxHSo1FW40GnfT1CMJG/HoRNWS02j5cxPej+zbTaUZAdd+/8JvL/jh92t/Ew9XWmXOhR6PeIr1iH82", + "n0X+YG7zHQAA//9fnz6pkQEAAA==", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/issues/issue-1529/strict-fiber/spec.yaml b/internal/test/issues/issue-1529/strict-fiber/spec.yaml new file mode 100644 index 0000000000..ca7aae80a3 --- /dev/null +++ b/internal/test/issues/issue-1529/strict-fiber/spec.yaml @@ -0,0 +1,21 @@ +openapi: "3.0.1" +components: + schemas: + Test: + type: object +paths: + /test: + get: + operationId: test + responses: + 200: + content: + application/json: + schema: + $ref: "#/components/schemas/Test" + application/json; profile="Foo": + schema: + $ref: "#/components/schemas/Test" + application/json; profile="Bar": + schema: + $ref: "#/components/schemas/Test" diff --git a/internal/test/issues/issue-1529/strict-iris/config.yaml b/internal/test/issues/issue-1529/strict-iris/config.yaml new file mode 100644 index 0000000000..835b035300 --- /dev/null +++ b/internal/test/issues/issue-1529/strict-iris/config.yaml @@ -0,0 +1,9 @@ +# yaml-language-server: $schema=../../../../../configuration-schema.json +package: issue1529 +generate: + client: true + models: true + embedded-spec: true + iris-server: true + strict-server: true +output: issue1529.gen.go diff --git a/internal/test/issues/issue-1529/strict-iris/doc.go b/internal/test/issues/issue-1529/strict-iris/doc.go new file mode 100644 index 0000000000..4bf78249fa --- /dev/null +++ b/internal/test/issues/issue-1529/strict-iris/doc.go @@ -0,0 +1,3 @@ +package issue1529 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/issues/issue-1529/strict-iris/issue1529.gen.go b/internal/test/issues/issue-1529/strict-iris/issue1529.gen.go new file mode 100644 index 0000000000..a214576e80 --- /dev/null +++ b/internal/test/issues/issue-1529/strict-iris/issue1529.gen.go @@ -0,0 +1,466 @@ +// Package issue1529 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue1529 + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/kataras/iris/v12" + strictiris "github.com/oapi-codegen/runtime/strictmiddleware/iris" +) + +// Test defines model for Test. +type Test = map[string]interface{} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // Test request + Test(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) Test(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewTestRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewTestRequest generates requests for Test +func NewTestRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/test") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // TestWithResponse request + TestWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*TestResponse, error) +} + +type TestResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Test + ApplicationjsonProfileBar200 *Test + ApplicationjsonProfileFoo200 *Test +} + +// Status returns HTTPResponse.Status +func (r TestResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r TestResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// TestWithResponse request returning *TestResponse +func (c *ClientWithResponses) TestWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*TestResponse, error) { + rsp, err := c.Test(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseTestResponse(rsp) +} + +// ParseTestResponse parses an HTTP response from a TestWithResponse call +func ParseTestResponse(rsp *http.Response) (*TestResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &TestResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case rsp.Header.Get("Content-Type") == "application/json" && rsp.StatusCode == 200: + var dest Test + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case rsp.Header.Get("Content-Type") == "application/json; profile=\"Bar\"" && rsp.StatusCode == 200: + var dest Test + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.ApplicationjsonProfileBar200 = &dest + + case rsp.Header.Get("Content-Type") == "application/json; profile=\"Foo\"" && rsp.StatusCode == 200: + var dest Test + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.ApplicationjsonProfileFoo200 = &dest + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /test) + Test(ctx iris.Context) +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +type MiddlewareFunc iris.Handler + +// Test converts iris context to params. +func (w *ServerInterfaceWrapper) Test(ctx iris.Context) { + + // Invoke the callback with all the unmarshaled arguments + w.Handler.Test(ctx) +} + +// IrisServerOption is the option for iris server +type IrisServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router *iris.Application, si ServerInterface) { + RegisterHandlersWithOptions(router, si, IrisServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router *iris.Application, si ServerInterface, options IrisServerOptions) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.Get(options.BaseURL+"/test", wrapper.Test) + + router.Build() +} + +type TestRequestObject struct { +} + +type TestResponseObject interface { + VisitTestResponse(ctx iris.Context) error +} + +type Test200JSONResponse Test + +func (response Test200JSONResponse) VisitTestResponse(ctx iris.Context) error { + ctx.ResponseWriter().Header().Set("Content-Type", "application/json") + ctx.StatusCode(200) + + return ctx.JSON(&response) +} + +type Test200ApplicationJSONProfileBarResponse Test + +func (response Test200ApplicationJSONProfileBarResponse) VisitTestResponse(ctx iris.Context) error { + ctx.ResponseWriter().Header().Set("Content-Type", "application/json; profile=\"Bar\"") + ctx.StatusCode(200) + + return ctx.JSON(&response) +} + +type Test200ApplicationJSONProfileFooResponse Test + +func (response Test200ApplicationJSONProfileFooResponse) VisitTestResponse(ctx iris.Context) error { + ctx.ResponseWriter().Header().Set("Content-Type", "application/json; profile=\"Foo\"") + ctx.StatusCode(200) + + return ctx.JSON(&response) +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (GET /test) + Test(ctx context.Context, request TestRequestObject) (TestResponseObject, error) +} + +type StrictHandlerFunc = strictiris.StrictIrisHandlerFunc +type StrictMiddlewareFunc = strictiris.StrictIrisMiddlewareFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +// Test operation middleware +func (sh *strictHandler) Test(ctx iris.Context) { + var request TestRequestObject + + handler := func(ctx iris.Context, request interface{}) (interface{}, error) { + return sh.ssi.Test(ctx, request.(TestRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "Test") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } else if validResponse, ok := response.(TestResponseObject); ok { + if err := validResponse.VisitTestResponse(ctx); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else if response != nil { + ctx.Writef("Unexpected response type: %T", response) + return + } +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/6zPMUsEMRAF4L8iT8twWbWL2FgI9pbXxDjr5cjNDMlYyLL/XRIXFizFad4072NmQZKL", + "ChNbQ1jQ0okucayv1KynfSkhQN7OlAzrujpkngWBP0txECWOmhFwf5gOt3DQaKcheNuIDxohSjVaFn55", + "R/jxHSo1FW40GnfT1CMJG/HoRNWS02j5cxPej+zbTaUZAdd+/8JvL/jh92t/Ew9XWmXOhR6PeIr1iH82", + "n0X+YG7zHQAA//9fnz6pkQEAAA==", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/issues/issue-1529/strict-iris/spec.yaml b/internal/test/issues/issue-1529/strict-iris/spec.yaml new file mode 100644 index 0000000000..ca7aae80a3 --- /dev/null +++ b/internal/test/issues/issue-1529/strict-iris/spec.yaml @@ -0,0 +1,21 @@ +openapi: "3.0.1" +components: + schemas: + Test: + type: object +paths: + /test: + get: + operationId: test + responses: + 200: + content: + application/json: + schema: + $ref: "#/components/schemas/Test" + application/json; profile="Foo": + schema: + $ref: "#/components/schemas/Test" + application/json; profile="Bar": + schema: + $ref: "#/components/schemas/Test" diff --git a/internal/test/issues/issue-1530/config.yaml b/internal/test/issues/issue-1530/config.yaml new file mode 100644 index 0000000000..3cb9f3dd04 --- /dev/null +++ b/internal/test/issues/issue-1530/config.yaml @@ -0,0 +1,4 @@ +package: issue1530 +generate: + models: true +output: issue1530.gen.go \ No newline at end of file diff --git a/internal/test/issues/issue-1530/doc.go b/internal/test/issues/issue-1530/doc.go new file mode 100644 index 0000000000..c6a0133735 --- /dev/null +++ b/internal/test/issues/issue-1530/doc.go @@ -0,0 +1,3 @@ +package issue1530 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml issue1530.yaml diff --git a/internal/test/issues/issue-1530/issue1530.gen.go b/internal/test/issues/issue-1530/issue1530.gen.go new file mode 100644 index 0000000000..8a60251b2c --- /dev/null +++ b/internal/test/issues/issue-1530/issue1530.gen.go @@ -0,0 +1,126 @@ +// Package issue1530 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue1530 + +import ( + "encoding/json" + "errors" + + "github.com/oapi-codegen/runtime" +) + +// ConfigHttp defines model for ConfigHttp. +type ConfigHttp struct { + ConfigType string `json:"config_type"` + Host string `json:"host"` + Password *string `json:"password,omitempty"` + Port int `json:"port"` + User *string `json:"user,omitempty"` +} + +// ConfigSaveReq defines model for ConfigSaveReq. +type ConfigSaveReq struct { + union json.RawMessage +} + +// ConfigSsh defines model for ConfigSsh. +type ConfigSsh struct { + ConfigType string `json:"config_type"` + Host *string `json:"host,omitempty"` + Port *int `json:"port,omitempty"` + PrivateKey *string `json:"private_key,omitempty"` + User *string `json:"user,omitempty"` +} + +// PostConfigJSONRequestBody defines body for PostConfig for application/json ContentType. +type PostConfigJSONRequestBody = ConfigSaveReq + +// AsConfigHttp returns the union data inside the ConfigSaveReq as a ConfigHttp +func (t ConfigSaveReq) AsConfigHttp() (ConfigHttp, error) { + var body ConfigHttp + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromConfigHttp overwrites any union data inside the ConfigSaveReq as the provided ConfigHttp +func (t *ConfigSaveReq) FromConfigHttp(v ConfigHttp) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeConfigHttp performs a merge with any union data inside the ConfigSaveReq, using the provided ConfigHttp +func (t *ConfigSaveReq) MergeConfigHttp(v ConfigHttp) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsConfigSsh returns the union data inside the ConfigSaveReq as a ConfigSsh +func (t ConfigSaveReq) AsConfigSsh() (ConfigSsh, error) { + var body ConfigSsh + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromConfigSsh overwrites any union data inside the ConfigSaveReq as the provided ConfigSsh +func (t *ConfigSaveReq) FromConfigSsh(v ConfigSsh) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeConfigSsh performs a merge with any union data inside the ConfigSaveReq, using the provided ConfigSsh +func (t *ConfigSaveReq) MergeConfigSsh(v ConfigSsh) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t ConfigSaveReq) Discriminator() (string, error) { + var discriminator struct { + Discriminator string `json:"config_type"` + } + err := json.Unmarshal(t.union, &discriminator) + return discriminator.Discriminator, err +} + +func (t ConfigSaveReq) ValueByDiscriminator() (interface{}, error) { + discriminator, err := t.Discriminator() + if err != nil { + return nil, err + } + switch discriminator { + case "another_server": + return t.AsConfigHttp() + case "apache_server": + return t.AsConfigHttp() + case "ssh_server": + return t.AsConfigSsh() + case "web_server": + return t.AsConfigHttp() + default: + return nil, errors.New("unknown discriminator value: " + discriminator) + } +} + +func (t ConfigSaveReq) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *ConfigSaveReq) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} diff --git a/internal/test/issues/issue-1530/issue1530.yaml b/internal/test/issues/issue-1530/issue1530.yaml new file mode 100644 index 0000000000..222d074b7b --- /dev/null +++ b/internal/test/issues/issue-1530/issue1530.yaml @@ -0,0 +1,57 @@ +paths: + /config: + post: + summary: Save configuration + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/ConfigSaveReq" + responses: + "200": + description: Configuration saved successfully +components: + schemas: + ConfigHttp: + type: object + properties: + config_type: + type: string + host: + type: string + port: + type: integer + user: + type: string + password: + type: string + required: + - config_type + - host + - port + ConfigSaveReq: + oneOf: + - $ref: "#/components/schemas/ConfigHttp" + - $ref: "#/components/schemas/ConfigSsh" + discriminator: + propertyName: config_type + mapping: + ssh_server: "#/components/schemas/ConfigSsh" + apache_server: "#/components/schemas/ConfigHttp" + web_server: "#/components/schemas/ConfigHttp" + another_server: "#/components/schemas/ConfigHttp" + ConfigSsh: + type: object + properties: + config_type: + type: string + host: + type: string + port: + type: integer + user: + type: string + private_key: + type: string + required: + - config_type \ No newline at end of file diff --git a/internal/test/issues/issue-1530/issue1530_test.go b/internal/test/issues/issue-1530/issue1530_test.go new file mode 100644 index 0000000000..a23f7ed2d8 --- /dev/null +++ b/internal/test/issues/issue-1530/issue1530_test.go @@ -0,0 +1,51 @@ +package issue1530_test + +import ( + "testing" + + issue1530 "github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-1530" + "github.com/stretchr/testify/require" +) + +func TestIssue1530(t *testing.T) { + httpConfigTypes := []string{ + "another_server", + "apache_server", + "web_server", + } + + for _, configType := range httpConfigTypes { + t.Run("http-"+configType, func(t *testing.T) { + saveReq := issue1530.ConfigSaveReq{} + err := saveReq.FromConfigHttp(issue1530.ConfigHttp{ + ConfigType: configType, + Host: "example.com", + }) + require.NoError(t, err) + + cfg, err := saveReq.AsConfigHttp() + require.NoError(t, err) + require.Equal(t, configType, cfg.ConfigType) + + cfgByDiscriminator, err := saveReq.ValueByDiscriminator() + require.NoError(t, err) + require.Equal(t, cfg, cfgByDiscriminator) + }) + } + + t.Run("ssh", func(t *testing.T) { + saveReq := issue1530.ConfigSaveReq{} + err := saveReq.FromConfigSsh(issue1530.ConfigSsh{ + ConfigType: "ssh_server", + }) + require.NoError(t, err) + + cfg, err := saveReq.AsConfigSsh() + require.NoError(t, err) + require.Equal(t, "ssh_server", cfg.ConfigType) + + cfgByDiscriminator, err := saveReq.ValueByDiscriminator() + require.NoError(t, err) + require.Equal(t, cfg, cfgByDiscriminator) + }) +} diff --git a/internal/test/issues/issue-1676/api.yaml b/internal/test/issues/issue-1676/api.yaml new file mode 100644 index 0000000000..717d75afd9 --- /dev/null +++ b/internal/test/issues/issue-1676/api.yaml @@ -0,0 +1,17 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Issue 1676 +paths: + /ping: + get: + responses: + '200': + headers: + MyHeader: + schema: + type: string + content: + text/plain: + schema: + type: string diff --git a/internal/test/issues/issue-1676/cfg.yaml b/internal/test/issues/issue-1676/cfg.yaml new file mode 100644 index 0000000000..964454fe2f --- /dev/null +++ b/internal/test/issues/issue-1676/cfg.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=../../../../../configuration-schema.json +package: issue1676 +output: ping.gen.go +generate: + models: true + gorilla-server: true + strict-server: true diff --git a/internal/test/issues/issue-1676/generate.go b/internal/test/issues/issue-1676/generate.go new file mode 100644 index 0000000000..49aa70b8a9 --- /dev/null +++ b/internal/test/issues/issue-1676/generate.go @@ -0,0 +1,3 @@ +package issue1676 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/internal/test/issues/issue-1676/ping.gen.go b/internal/test/issues/issue-1676/ping.gen.go new file mode 100644 index 0000000000..dcfddc3bc6 --- /dev/null +++ b/internal/test/issues/issue-1676/ping.gen.go @@ -0,0 +1,246 @@ +// Package issue1676 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue1676 + +import ( + "context" + "fmt" + "net/http" + + "github.com/gorilla/mux" + strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /ping) + GetPing(w http.ResponseWriter, r *http.Request) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetPing operation middleware +func (siw *ServerInterfaceWrapper) GetPing(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetPing(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{}) +} + +type GorillaServerOptions struct { + BaseURL string + BaseRouter *mux.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r *mux.Router) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r *mux.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options GorillaServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = mux.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.HandleFunc(options.BaseURL+"/ping", wrapper.GetPing).Methods("GET") + + return r +} + +type GetPingRequestObject struct { +} + +type GetPingResponseObject interface { + VisitGetPingResponse(w http.ResponseWriter) error +} + +type GetPing200ResponseHeaders struct { + MyHeader string +} + +type GetPing200TextResponse struct { + Body string + Headers GetPing200ResponseHeaders +} + +func (response GetPing200TextResponse) VisitGetPingResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.Header().Set("MyHeader", fmt.Sprint(response.Headers.MyHeader)) + w.WriteHeader(200) + + _, err := w.Write([]byte(response.Body)) + return err +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (GET /ping) + GetPing(ctx context.Context, request GetPingRequestObject) (GetPingResponseObject, error) +} + +type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc +type StrictMiddlewareFunc = strictnethttp.StrictHTTPMiddlewareFunc + +type StrictHTTPServerOptions struct { + RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictHTTPServerOptions{ + RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + }, + ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusInternalServerError) + }, + }} +} + +func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictHTTPServerOptions) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: options} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc + options StrictHTTPServerOptions +} + +// GetPing operation middleware +func (sh *strictHandler) GetPing(w http.ResponseWriter, r *http.Request) { + var request GetPingRequestObject + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetPing(ctx, request.(GetPingRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetPing") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetPingResponseObject); ok { + if err := validResponse.VisitGetPingResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} diff --git a/internal/test/issues/issue-2031/config.yaml b/internal/test/issues/issue-2031/config.yaml new file mode 100644 index 0000000000..495396d560 --- /dev/null +++ b/internal/test/issues/issue-2031/config.yaml @@ -0,0 +1,4 @@ +package: issue2031 +generate: + models: true +output: issue2031.gen.go diff --git a/internal/test/issues/issue-2031/generate.go b/internal/test/issues/issue-2031/generate.go new file mode 100644 index 0000000000..1e29681627 --- /dev/null +++ b/internal/test/issues/issue-2031/generate.go @@ -0,0 +1,3 @@ +package issue2031 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml openapi.yaml diff --git a/internal/test/issues/issue-2031/issue2031.gen.go b/internal/test/issues/issue-2031/issue2031.gen.go new file mode 100644 index 0000000000..1ae12da963 --- /dev/null +++ b/internal/test/issues/issue-2031/issue2031.gen.go @@ -0,0 +1,83 @@ +// Package issue2031 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue2031 + +import ( + "encoding/json" + "fmt" +) + +// ArrayContainer defines model for ArrayContainer. +type ArrayContainer struct { + Values []string `json:"values,omitempty"` + AdditionalProperties map[string]interface{} `json:"-"` +} + +// Getter for additional properties for ArrayContainer. Returns the specified +// element and whether it was found +func (a ArrayContainer) Get(fieldName string) (value interface{}, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for ArrayContainer +func (a *ArrayContainer) Set(fieldName string, value interface{}) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]interface{}) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for ArrayContainer to handle AdditionalProperties +func (a *ArrayContainer) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["values"]; found { + err = json.Unmarshal(raw, &a.Values) + if err != nil { + return fmt.Errorf("error reading 'values': %w", err) + } + delete(object, "values") + } + + if len(object) != 0 { + a.AdditionalProperties = make(map[string]interface{}) + for fieldName, fieldBuf := range object { + var fieldVal interface{} + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for ArrayContainer to handle AdditionalProperties +func (a ArrayContainer) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + if a.Values != nil { + object["values"], err = json.Marshal(a.Values) + if err != nil { + return nil, fmt.Errorf("error marshaling 'values': %w", err) + } + } + + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) +} diff --git a/internal/test/issues/issue-2031/issue2031_test.go b/internal/test/issues/issue-2031/issue2031_test.go new file mode 100644 index 0000000000..fe631a8bcf --- /dev/null +++ b/internal/test/issues/issue-2031/issue2031_test.go @@ -0,0 +1,18 @@ +package issue2031 + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMarshal(t *testing.T) { + value := ArrayContainer{} + content, err := json.Marshal(value) + require.NoError(t, err) + // the _optional array_ should be _omitted_ when null, not marshaled as null + // (which is not valid per the schema) + assert.Equal(t, "{}", string(content)) +} diff --git a/internal/test/issues/issue-2031/openapi.yaml b/internal/test/issues/issue-2031/openapi.yaml new file mode 100644 index 0000000000..54aa5eb404 --- /dev/null +++ b/internal/test/issues/issue-2031/openapi.yaml @@ -0,0 +1,27 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Issue 2031 +paths: + /test: + get: + responses: + "200": + description: A list of strings + content: + application/json: + schema: + $ref: "#/components/schemas/ArrayContainer" +components: + schemas: + ArrayContainer: + type: object + # enabling additionalProperties is required to expose one variant of the bug + additionalProperties: true + properties: + # NOTE: the array property is NOT required and NOT nullable + values: + x-go-type-skip-optional-pointer: true + type: array + items: + type: string diff --git a/internal/test/issues/issue-2031/prefer/config.yaml b/internal/test/issues/issue-2031/prefer/config.yaml new file mode 100644 index 0000000000..4ec155a835 --- /dev/null +++ b/internal/test/issues/issue-2031/prefer/config.yaml @@ -0,0 +1,7 @@ +package: issue2031 +generate: + models: true + client: true +output-options: + prefer-skip-optional-pointer: true +output: issue2031.gen.go diff --git a/internal/test/issues/issue-2031/prefer/generate.go b/internal/test/issues/issue-2031/prefer/generate.go new file mode 100644 index 0000000000..1e29681627 --- /dev/null +++ b/internal/test/issues/issue-2031/prefer/generate.go @@ -0,0 +1,3 @@ +package issue2031 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml openapi.yaml diff --git a/internal/test/issues/issue-2031/prefer/issue2031.gen.go b/internal/test/issues/issue-2031/prefer/issue2031.gen.go new file mode 100644 index 0000000000..6144c0b4be --- /dev/null +++ b/internal/test/issues/issue-2031/prefer/issue2031.gen.go @@ -0,0 +1,251 @@ +// Package issue2031 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue2031 + +import ( + "context" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/oapi-codegen/runtime" +) + +// GetTestParams defines parameters for GetTest. +type GetTestParams struct { + UserIds []int `form:"user_ids[],omitempty" json:"user_ids[],omitempty"` +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetTest request + GetTest(ctx context.Context, params *GetTestParams, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) GetTest(ctx context.Context, params *GetTestParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetTestRequest(c.Server, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetTestRequest generates requests for GetTest +func NewGetTestRequest(server string, params *GetTestParams) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/test") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.UserIds != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "user_ids[]", params.UserIds, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "array", Format: ""}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetTestWithResponse request + GetTestWithResponse(ctx context.Context, params *GetTestParams, reqEditors ...RequestEditorFn) (*GetTestResponse, error) +} + +type GetTestResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r GetTestResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetTestResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// GetTestWithResponse request returning *GetTestResponse +func (c *ClientWithResponses) GetTestWithResponse(ctx context.Context, params *GetTestParams, reqEditors ...RequestEditorFn) (*GetTestResponse, error) { + rsp, err := c.GetTest(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetTestResponse(rsp) +} + +// ParseGetTestResponse parses an HTTP response from a GetTestWithResponse call +func ParseGetTestResponse(rsp *http.Response) (*GetTestResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetTestResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} diff --git a/internal/test/issues/issue-2031/prefer/issue2031_test.go b/internal/test/issues/issue-2031/prefer/issue2031_test.go new file mode 100644 index 0000000000..cd0c55c8dd --- /dev/null +++ b/internal/test/issues/issue-2031/prefer/issue2031_test.go @@ -0,0 +1,63 @@ +package issue2031 + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewGetTestRequest(t *testing.T) { + t.Run("does not add the user_ids[] parameter if zero value", func(t *testing.T) { + params := GetTestParams{} + + req, err := NewGetTestRequest("https://localhost", ¶ms) + require.NoError(t, err) + + assert.Equal(t, "https://localhost/test", req.URL.String()) + }) + + t.Run("does not add the user_ids[] parameter if nil", func(t *testing.T) { + params := GetTestParams{ + UserIds: nil, + } + + req, err := NewGetTestRequest("https://localhost", ¶ms) + require.NoError(t, err) + + assert.Equal(t, "https://localhost/test", req.URL.String()) + }) + + t.Run("adds the user_ids[] parameter if an explicitly initialised empty array", func(t *testing.T) { + params := GetTestParams{ + UserIds: []int{}, + } + + req, err := NewGetTestRequest("https://localhost", ¶ms) + require.NoError(t, err) + + assert.Equal(t, "https://localhost/test?user_ids%5B%5D=", req.URL.String()) + }) + + t.Run("adds the user_ids[] parameter if array contains a value", func(t *testing.T) { + params := GetTestParams{ + UserIds: []int{1}, + } + + req, err := NewGetTestRequest("https://localhost", ¶ms) + require.NoError(t, err) + + assert.Equal(t, "https://localhost/test?user_ids%5B%5D=1", req.URL.String()) + }) + + t.Run("handles multiple user_ids[] parameters", func(t *testing.T) { + params := GetTestParams{ + UserIds: []int{1, 100}, + } + + req, err := NewGetTestRequest("https://localhost", ¶ms) + require.NoError(t, err) + + assert.Equal(t, "https://localhost/test?user_ids%5B%5D=1&user_ids%5B%5D=100", req.URL.String()) + }) +} diff --git a/internal/test/issues/issue-2031/prefer/openapi.yaml b/internal/test/issues/issue-2031/prefer/openapi.yaml new file mode 100644 index 0000000000..d88d8df2fb --- /dev/null +++ b/internal/test/issues/issue-2031/prefer/openapi.yaml @@ -0,0 +1,17 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Issue 2031 +paths: + /test: + get: + parameters: + - name: "user_ids[]" + in: query + schema: + type: array + items: + type: integer + style: form + explode: true + required: false diff --git a/internal/test/issues/issue-2113/common/spec.yaml b/internal/test/issues/issue-2113/common/spec.yaml new file mode 100644 index 0000000000..dded5776fc --- /dev/null +++ b/internal/test/issues/issue-2113/common/spec.yaml @@ -0,0 +1,22 @@ +openapi: "3.0.4" +info: + title: Common + version: "0.0.1" +paths: {} +components: + schemas: + ProblemDetails: + type: object + properties: + title: + type: string + status: + type: integer + required: [title, status] + responses: + StandardError: + description: Error response + content: + application/json: + schema: + $ref: "#/components/schemas/ProblemDetails" diff --git a/internal/test/issues/issue-2113/config.api.yaml b/internal/test/issues/issue-2113/config.api.yaml new file mode 100644 index 0000000000..754af9fa49 --- /dev/null +++ b/internal/test/issues/issue-2113/config.api.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +generate: + chi-server: true + strict-server: true + models: true +import-mapping: + ./common/spec.yaml: github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-2113/gen/common +output: gen/api/api.gen.go +output-options: + skip-prune: true diff --git a/internal/test/issues/issue-2113/config.common.yaml b/internal/test/issues/issue-2113/config.common.yaml new file mode 100644 index 0000000000..f9803ed307 --- /dev/null +++ b/internal/test/issues/issue-2113/config.common.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: common +generate: + models: true +output: gen/common/common.gen.go +output-options: + skip-prune: true diff --git a/internal/test/issues/issue-2113/doc.go b/internal/test/issues/issue-2113/doc.go new file mode 100644 index 0000000000..89ddb3d195 --- /dev/null +++ b/internal/test/issues/issue-2113/doc.go @@ -0,0 +1,4 @@ +package issue2113 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.common.yaml common/spec.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.api.yaml spec.yaml diff --git a/internal/test/issues/issue-2113/gen/api/api.gen.go b/internal/test/issues/issue-2113/gen/api/api.gen.go new file mode 100644 index 0000000000..6562a7f30d --- /dev/null +++ b/internal/test/issues/issue-2113/gen/api/api.gen.go @@ -0,0 +1,271 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/go-chi/chi/v5" + externalRef0 "github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-2113/gen/common" + strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /things) + ListThings(w http.ResponseWriter, r *http.Request) +} + +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct{} + +// (GET /things) +func (_ Unimplemented) ListThings(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// ListThings operation middleware +func (siw *ServerInterfaceWrapper) ListThings(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListThings(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{}) +} + +type ChiServerOptions struct { + BaseURL string + BaseRouter chi.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = chi.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/things", wrapper.ListThings) + }) + + return r +} + +type ListThingsRequestObject struct { +} + +type ListThingsResponseObject interface { + VisitListThingsResponse(w http.ResponseWriter) error +} + +type ListThings200JSONResponse []string + +func (response ListThings200JSONResponse) VisitListThingsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type ListThings400JSONResponse struct{ externalRef0.StandardError } + +func (response ListThings400JSONResponse) VisitListThingsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type ListThingsdefaultJSONResponse struct { + Body externalRef0.ProblemDetails + StatusCode int +} + +func (response ListThingsdefaultJSONResponse) VisitListThingsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(response.StatusCode) + + return json.NewEncoder(w).Encode(response.Body) +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (GET /things) + ListThings(ctx context.Context, request ListThingsRequestObject) (ListThingsResponseObject, error) +} + +type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc +type StrictMiddlewareFunc = strictnethttp.StrictHTTPMiddlewareFunc + +type StrictHTTPServerOptions struct { + RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictHTTPServerOptions{ + RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + }, + ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusInternalServerError) + }, + }} +} + +func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictHTTPServerOptions) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: options} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc + options StrictHTTPServerOptions +} + +// ListThings operation middleware +func (sh *strictHandler) ListThings(w http.ResponseWriter, r *http.Request) { + var request ListThingsRequestObject + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.ListThings(ctx, request.(ListThingsRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ListThings") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(ListThingsResponseObject); ok { + if err := validResponse.VisitListThingsResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} diff --git a/internal/test/issues/issue-2113/gen/common/common.gen.go b/internal/test/issues/issue-2113/gen/common/common.gen.go new file mode 100644 index 0000000000..8d16de5f8f --- /dev/null +++ b/internal/test/issues/issue-2113/gen/common/common.gen.go @@ -0,0 +1,13 @@ +// Package common provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package common + +// ProblemDetails defines model for ProblemDetails. +type ProblemDetails struct { + Status int `json:"status"` + Title string `json:"title"` +} + +// StandardError defines model for StandardError. +type StandardError = ProblemDetails diff --git a/internal/test/issues/issue-2113/issue_test.go b/internal/test/issues/issue-2113/issue_test.go new file mode 100644 index 0000000000..5d5dbbe48c --- /dev/null +++ b/internal/test/issues/issue-2113/issue_test.go @@ -0,0 +1,22 @@ +package issue2113 + +import ( + "testing" + + "github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-2113/gen/api" + "github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-2113/gen/common" +) + +// TestExternalRefInResponse verifies that a $ref to an external +// components/responses object correctly qualifies the schema type +// with the external package import. See +// https://github.com/oapi-codegen/oapi-codegen/issues/2113 +func TestExternalRefInResponse(t *testing.T) { + // This will fail to compile if the generated code uses + // ProblemDetails instead of common.ProblemDetails (via the + // externalRef alias) in the default response type. + _ = api.ListThingsdefaultJSONResponse{ + Body: common.ProblemDetails{Title: "err", Status: 500}, + StatusCode: 500, + } +} diff --git a/internal/test/issues/issue-2113/spec.yaml b/internal/test/issues/issue-2113/spec.yaml new file mode 100644 index 0000000000..9f4f50d95d --- /dev/null +++ b/internal/test/issues/issue-2113/spec.yaml @@ -0,0 +1,21 @@ +openapi: "3.0.4" +info: + title: API + version: "0.0.1" +paths: + /things: + get: + operationId: listThings + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + type: string + "400": + $ref: "./common/spec.yaml#/components/responses/StandardError" + default: + $ref: "./common/spec.yaml#/components/responses/StandardError" diff --git a/internal/test/issues/issue-2185/config.yaml b/internal/test/issues/issue-2185/config.yaml new file mode 100644 index 0000000000..71a7ba132e --- /dev/null +++ b/internal/test/issues/issue-2185/config.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: issue2185 +output: issue2185.gen.go +generate: + models: true +output-options: + skip-prune: true + nullable-type: true diff --git a/internal/test/issues/issue-2185/generate.go b/internal/test/issues/issue-2185/generate.go new file mode 100644 index 0000000000..9f5224eaeb --- /dev/null +++ b/internal/test/issues/issue-2185/generate.go @@ -0,0 +1,3 @@ +package issue2185 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/issues/issue-2185/issue2185.gen.go b/internal/test/issues/issue-2185/issue2185.gen.go new file mode 100644 index 0000000000..6474c9c4d0 --- /dev/null +++ b/internal/test/issues/issue-2185/issue2185.gen.go @@ -0,0 +1,13 @@ +// Package issue2185 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue2185 + +import ( + "github.com/oapi-codegen/nullable" +) + +// Container defines model for Container. +type Container struct { + MayBeNull []nullable.Nullable[string] `json:"may-be-null"` +} diff --git a/internal/test/issues/issue-2185/issue2185_test.go b/internal/test/issues/issue-2185/issue2185_test.go new file mode 100644 index 0000000000..cc98e2b4d3 --- /dev/null +++ b/internal/test/issues/issue-2185/issue2185_test.go @@ -0,0 +1,19 @@ +package issue2185 + +import ( + "testing" + + "github.com/oapi-codegen/nullable" + "github.com/stretchr/testify/require" +) + +func TestContainer_UsesNullableType(t *testing.T) { + c := Container{ + MayBeNull: []nullable.Nullable[string]{ + nullable.NewNullNullable[string](), + }, + } + + require.Len(t, c.MayBeNull, 1) + require.True(t, c.MayBeNull[0].IsNull()) +} diff --git a/internal/test/issues/issue-2185/spec.yaml b/internal/test/issues/issue-2185/spec.yaml new file mode 100644 index 0000000000..814048bc8f --- /dev/null +++ b/internal/test/issues/issue-2185/spec.yaml @@ -0,0 +1,15 @@ +openapi: "3.0.3" +info: + title: test + version: 1.0.0 +components: + schemas: + Container: + required: + - "may-be-null" + properties: + "may-be-null": + type: array + items: + type: string + nullable: true diff --git a/internal/test/issues/issue-2190/config.yaml b/internal/test/issues/issue-2190/config.yaml new file mode 100644 index 0000000000..da89951cdc --- /dev/null +++ b/internal/test/issues/issue-2190/config.yaml @@ -0,0 +1,9 @@ +package: issue2190 +output: issue2190.gen.go +generate: + std-http-server: true + strict-server: true + models: true + client: true +output-options: + nullable-type: true diff --git a/internal/test/issues/issue-2190/generate.go b/internal/test/issues/issue-2190/generate.go new file mode 100644 index 0000000000..74169c3fe3 --- /dev/null +++ b/internal/test/issues/issue-2190/generate.go @@ -0,0 +1,3 @@ +package issue2190 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/issues/issue-2190/issue2190.gen.go b/internal/test/issues/issue-2190/issue2190.gen.go new file mode 100644 index 0000000000..c22e32724c --- /dev/null +++ b/internal/test/issues/issue-2190/issue2190.gen.go @@ -0,0 +1,486 @@ +//go:build go1.22 + +// Package issue2190 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue2190 + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp" +) + +// Success defines model for Success. +type Success = string + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetTest request + GetTest(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) GetTest(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetTestRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetTestRequest generates requests for GetTest +func NewGetTestRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/test") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetTestWithResponse request + GetTestWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetTestResponse, error) +} + +type GetTestResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Success +} + +// Status returns HTTPResponse.Status +func (r GetTestResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetTestResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// GetTestWithResponse request returning *GetTestResponse +func (c *ClientWithResponses) GetTestWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetTestResponse, error) { + rsp, err := c.GetTest(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetTestResponse(rsp) +} + +// ParseGetTestResponse parses an HTTP response from a GetTestWithResponse call +func ParseGetTestResponse(rsp *http.Response) (*GetTestResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetTestResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Success + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /v1/test) + GetTest(w http.ResponseWriter, r *http.Request) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetTest operation middleware +func (siw *ServerInterfaceWrapper) GetTest(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetTest(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{}) +} + +// ServeMux is an abstraction of http.ServeMux. +type ServeMux interface { + HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) + ServeHTTP(w http.ResponseWriter, r *http.Request) +} + +type StdHTTPServerOptions struct { + BaseURL string + BaseRouter ServeMux + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, m ServeMux) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{ + BaseRouter: m, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, m ServeMux, baseURL string) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{ + BaseURL: baseURL, + BaseRouter: m, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.Handler { + m := options.BaseRouter + + if m == nil { + m = http.NewServeMux() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + m.HandleFunc("GET "+options.BaseURL+"/v1/test", wrapper.GetTest) + + return m +} + +type SuccessJSONResponse string + +type UnauthorizedTextResponse string + +type GetTestRequestObject struct { +} + +type GetTestResponseObject interface { + VisitGetTestResponse(w http.ResponseWriter) error +} + +type GetTest200JSONResponse struct{ SuccessJSONResponse } + +func (response GetTest200JSONResponse) VisitGetTestResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetTest401TextResponse UnauthorizedTextResponse + +func (response GetTest401TextResponse) VisitGetTestResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(401) + + _, err := w.Write([]byte(response)) + return err +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (GET /v1/test) + GetTest(ctx context.Context, request GetTestRequestObject) (GetTestResponseObject, error) +} + +type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc +type StrictMiddlewareFunc = strictnethttp.StrictHTTPMiddlewareFunc + +type StrictHTTPServerOptions struct { + RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictHTTPServerOptions{ + RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + }, + ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusInternalServerError) + }, + }} +} + +func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictHTTPServerOptions) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: options} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc + options StrictHTTPServerOptions +} + +// GetTest operation middleware +func (sh *strictHandler) GetTest(w http.ResponseWriter, r *http.Request) { + var request GetTestRequestObject + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetTest(ctx, request.(GetTestRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetTest") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetTestResponseObject); ok { + if err := validResponse.VisitGetTestResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} diff --git a/internal/test/issues/issue-2190/issue2190_test.go b/internal/test/issues/issue-2190/issue2190_test.go new file mode 100644 index 0000000000..8dc7a0b075 --- /dev/null +++ b/internal/test/issues/issue-2190/issue2190_test.go @@ -0,0 +1,37 @@ +package issue2190 + +import ( + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestGetTest401TextResponse verifies that the generated VisitGetTestResponse +// method on GetTest401TextResponse produces a valid text/plain 401 response. +// This is a regression test for https://github.com/oapi-codegen/oapi-codegen/issues/2190 +// where the generated code tried to do []byte(response) on a struct type, +// which does not compile. +func TestGetTest401TextResponse(t *testing.T) { + resp := GetTest401TextResponse("Unauthorized") + w := httptest.NewRecorder() + + err := resp.VisitGetTestResponse(w) + require.NoError(t, err) + assert.Equal(t, 401, w.Code) + assert.Equal(t, "text/plain", w.Header().Get("Content-Type")) + assert.Equal(t, "Unauthorized", w.Body.String()) +} + +// TestGetTest200JSONResponse verifies that the 200 JSON response path also works. +func TestGetTest200JSONResponse(t *testing.T) { + resp := GetTest200JSONResponse{SuccessJSONResponse("hello")} + w := httptest.NewRecorder() + + err := resp.VisitGetTestResponse(w) + require.NoError(t, err) + assert.Equal(t, 200, w.Code) + assert.Equal(t, "application/json", w.Header().Get("Content-Type")) + assert.Contains(t, w.Body.String(), "hello") +} diff --git a/internal/test/issues/issue-2190/spec.yaml b/internal/test/issues/issue-2190/spec.yaml new file mode 100644 index 0000000000..2844100e3d --- /dev/null +++ b/internal/test/issues/issue-2190/spec.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.3 +info: + title: test + version: 1.0.0 +servers: + - url: https://te.st +paths: + /v1/test: + get: + operationId: GetTest + responses: + "200": + $ref: "#/components/responses/Success" + "401": + $ref: "#/components/responses/Unauthorized" +components: + responses: + Success: + description: Success + content: + application/json: + schema: + type: string + Unauthorized: + description: Unauthorized + content: + text/plain: + schema: + type: string + example: "Unauthorized" diff --git a/internal/test/issues/issue-2232/config.yaml b/internal/test/issues/issue-2232/config.yaml new file mode 100644 index 0000000000..6368de1d4d --- /dev/null +++ b/internal/test/issues/issue-2232/config.yaml @@ -0,0 +1,5 @@ +package: issue2232 +output: issue2232.gen.go +generate: + std-http-server: true + models: true diff --git a/internal/test/issues/issue-2232/generate.go b/internal/test/issues/issue-2232/generate.go new file mode 100644 index 0000000000..f6767c10e1 --- /dev/null +++ b/internal/test/issues/issue-2232/generate.go @@ -0,0 +1,3 @@ +package issue2232 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/issues/issue-2232/issue2232.gen.go b/internal/test/issues/issue-2232/issue2232.gen.go new file mode 100644 index 0000000000..1820ba5f29 --- /dev/null +++ b/internal/test/issues/issue-2232/issue2232.gen.go @@ -0,0 +1,260 @@ +//go:build go1.22 + +// Package issue2232 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue2232 + +import ( + "fmt" + "net/http" + + "github.com/oapi-codegen/runtime" +) + +// Defines values for GetEndpointParamsEnvParamLevel. +const ( + GetEndpointParamsEnvParamLevelDev GetEndpointParamsEnvParamLevel = "dev" + GetEndpointParamsEnvParamLevelLive GetEndpointParamsEnvParamLevel = "live" +) + +// Valid indicates whether the value is a known member of the GetEndpointParamsEnvParamLevel enum. +func (e GetEndpointParamsEnvParamLevel) Valid() bool { + switch e { + case GetEndpointParamsEnvParamLevelDev: + return true + case GetEndpointParamsEnvParamLevelLive: + return true + default: + return false + } +} + +// Defines values for GetEndpointParamsEnvSchemaLevel. +const ( + GetEndpointParamsEnvSchemaLevelDev GetEndpointParamsEnvSchemaLevel = "dev" + GetEndpointParamsEnvSchemaLevelLive GetEndpointParamsEnvSchemaLevel = "live" +) + +// Valid indicates whether the value is a known member of the GetEndpointParamsEnvSchemaLevel enum. +func (e GetEndpointParamsEnvSchemaLevel) Valid() bool { + switch e { + case GetEndpointParamsEnvSchemaLevelDev: + return true + case GetEndpointParamsEnvSchemaLevelLive: + return true + default: + return false + } +} + +// GetEndpointParams defines parameters for GetEndpoint. +type GetEndpointParams struct { + EnvParamLevel GetEndpointParamsEnvParamLevel `form:"env_param_level" json:"env_param_level" validate:"required,oneof=dev live"` + EnvSchemaLevel GetEndpointParamsEnvSchemaLevel `form:"env_schema_level" json:"env_schema_level" validate:"required,oneof=dev live"` + Limit *int `form:"limit,omitempty" json:"limit,omitempty" validate:"min=0,max=100"` +} + +// GetEndpointParamsEnvParamLevel defines parameters for GetEndpoint. +type GetEndpointParamsEnvParamLevel string + +// GetEndpointParamsEnvSchemaLevel defines parameters for GetEndpoint. +type GetEndpointParamsEnvSchemaLevel string + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /v1/endpoint) + GetEndpoint(w http.ResponseWriter, r *http.Request, params GetEndpointParams) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetEndpoint operation middleware +func (siw *ServerInterfaceWrapper) GetEndpoint(w http.ResponseWriter, r *http.Request) { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params GetEndpointParams + + // ------------- Required query parameter "env_param_level" ------------- + + if paramValue := r.URL.Query().Get("env_param_level"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "env_param_level"}) + return + } + + err = runtime.BindQueryParameterWithOptions("form", true, true, "env_param_level", r.URL.Query(), ¶ms.EnvParamLevel, runtime.BindQueryParameterOptions{Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "env_param_level", Err: err}) + return + } + + // ------------- Required query parameter "env_schema_level" ------------- + + if paramValue := r.URL.Query().Get("env_schema_level"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "env_schema_level"}) + return + } + + err = runtime.BindQueryParameterWithOptions("form", true, true, "env_schema_level", r.URL.Query(), ¶ms.EnvSchemaLevel, runtime.BindQueryParameterOptions{Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "env_schema_level", Err: err}) + return + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "limit", r.URL.Query(), ¶ms.Limit, runtime.BindQueryParameterOptions{Type: "integer", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "limit", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetEndpoint(w, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{}) +} + +// ServeMux is an abstraction of http.ServeMux. +type ServeMux interface { + HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) + ServeHTTP(w http.ResponseWriter, r *http.Request) +} + +type StdHTTPServerOptions struct { + BaseURL string + BaseRouter ServeMux + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, m ServeMux) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{ + BaseRouter: m, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, m ServeMux, baseURL string) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{ + BaseURL: baseURL, + BaseRouter: m, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.Handler { + m := options.BaseRouter + + if m == nil { + m = http.NewServeMux() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + m.HandleFunc("GET "+options.BaseURL+"/v1/endpoint", wrapper.GetEndpoint) + + return m +} diff --git a/internal/test/issues/issue-2232/issue2232_test.go b/internal/test/issues/issue-2232/issue2232_test.go new file mode 100644 index 0000000000..a8ae51836e --- /dev/null +++ b/internal/test/issues/issue-2232/issue2232_test.go @@ -0,0 +1,41 @@ +package issue2232 + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestExtraTagsOnQueryParams verifies that x-oapi-codegen-extra-tags is applied +// to query parameter struct fields regardless of whether the extension is placed +// at the parameter level or at the schema level within the parameter. +// This is a regression test for https://github.com/oapi-codegen/oapi-codegen/issues/2232 +func TestExtraTagsOnQueryParams(t *testing.T) { + paramType := reflect.TypeOf(GetEndpointParams{}) + + t.Run("parameter-level extension", func(t *testing.T) { + field, ok := paramType.FieldByName("EnvParamLevel") + require.True(t, ok, "field EnvParamLevel should exist") + + assert.Equal(t, `required,oneof=dev live`, field.Tag.Get("validate"), + "x-oapi-codegen-extra-tags at parameter level should produce validate tag") + }) + + t.Run("schema-level extension", func(t *testing.T) { + field, ok := paramType.FieldByName("EnvSchemaLevel") + require.True(t, ok, "field EnvSchemaLevel should exist") + + assert.Equal(t, `required,oneof=dev live`, field.Tag.Get("validate"), + "x-oapi-codegen-extra-tags at schema level within a parameter should produce validate tag") + }) + + t.Run("schema-level extension on optional param", func(t *testing.T) { + field, ok := paramType.FieldByName("Limit") + require.True(t, ok, "field Limit should exist") + + assert.Equal(t, `min=0,max=100`, field.Tag.Get("validate"), + "x-oapi-codegen-extra-tags at schema level within an optional parameter should produce validate tag") + }) +} diff --git a/internal/test/issues/issue-2232/spec.yaml b/internal/test/issues/issue-2232/spec.yaml new file mode 100644 index 0000000000..0fea5cba83 --- /dev/null +++ b/internal/test/issues/issue-2232/spec.yaml @@ -0,0 +1,46 @@ +openapi: "3.0.3" +info: + title: test + version: 1.0.0 +paths: + /v1/endpoint: + get: + operationId: GetEndpoint + parameters: + - name: env_param_level + in: query + required: true + schema: + type: string + enum: + - dev + - live + x-oapi-codegen-extra-tags: + validate: "required,oneof=dev live" + - name: env_schema_level + in: query + required: true + schema: + type: string + enum: + - dev + - live + x-oapi-codegen-extra-tags: + validate: "required,oneof=dev live" + - name: limit + in: query + required: false + schema: + type: integer + x-oapi-codegen-extra-tags: + validate: "min=0,max=100" + responses: + "200": + description: Success + content: + application/json: + schema: + type: object + properties: + message: + type: string diff --git a/internal/test/issues/issue-2238/config.yaml b/internal/test/issues/issue-2238/config.yaml new file mode 100644 index 0000000000..cbb78b8d30 --- /dev/null +++ b/internal/test/issues/issue-2238/config.yaml @@ -0,0 +1,7 @@ +package: issue2238 +generate: + models: true + client: true +output-options: + prefer-skip-optional-pointer: true +output: issue2238.gen.go diff --git a/internal/test/issues/issue-2238/generate.go b/internal/test/issues/issue-2238/generate.go new file mode 100644 index 0000000000..5c10773236 --- /dev/null +++ b/internal/test/issues/issue-2238/generate.go @@ -0,0 +1,3 @@ +package issue2238 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml openapi.yaml diff --git a/internal/test/issues/issue-2238/issue2238.gen.go b/internal/test/issues/issue-2238/issue2238.gen.go new file mode 100644 index 0000000000..81f395ddfa --- /dev/null +++ b/internal/test/issues/issue-2238/issue2238.gen.go @@ -0,0 +1,262 @@ +// Package issue2238 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue2238 + +import ( + "context" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/oapi-codegen/runtime" +) + +// GetTestParams defines parameters for GetTest. +type GetTestParams struct { + XTags []string `json:"X-Tags,omitempty"` + Tags []string `form:"tags,omitempty" json:"tags,omitempty"` +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetTest request + GetTest(ctx context.Context, params *GetTestParams, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) GetTest(ctx context.Context, params *GetTestParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetTestRequest(c.Server, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetTestRequest generates requests for GetTest +func NewGetTestRequest(server string, params *GetTestParams) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/test") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + if params != nil { + + if params.XTags != nil { + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-Tags", params.XTags, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "array", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("X-Tags", headerParam0) + } + + } + + if params != nil { + + if params.Tags != nil { + var cookieParam0 string + + cookieParam0, err = runtime.StyleParamWithOptions("simple", true, "tags", params.Tags, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationCookie, Type: "array", Format: ""}) + if err != nil { + return nil, err + } + + cookie0 := &http.Cookie{ + Name: "tags", + Value: cookieParam0, + } + req.AddCookie(cookie0) + } + } + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetTestWithResponse request + GetTestWithResponse(ctx context.Context, params *GetTestParams, reqEditors ...RequestEditorFn) (*GetTestResponse, error) +} + +type GetTestResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r GetTestResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetTestResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// GetTestWithResponse request returning *GetTestResponse +func (c *ClientWithResponses) GetTestWithResponse(ctx context.Context, params *GetTestParams, reqEditors ...RequestEditorFn) (*GetTestResponse, error) { + rsp, err := c.GetTest(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetTestResponse(rsp) +} + +// ParseGetTestResponse parses an HTTP response from a GetTestWithResponse call +func ParseGetTestResponse(rsp *http.Response) (*GetTestResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetTestResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} diff --git a/internal/test/issues/issue-2238/issue2238_test.go b/internal/test/issues/issue-2238/issue2238_test.go new file mode 100644 index 0000000000..8c399efa52 --- /dev/null +++ b/internal/test/issues/issue-2238/issue2238_test.go @@ -0,0 +1,56 @@ +package issue2238 + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewGetTestRequest(t *testing.T) { + t.Run("nil header array param is not sent", func(t *testing.T) { + params := GetTestParams{ + XTags: nil, + } + + req, err := NewGetTestRequest("https://localhost", ¶ms) + require.NoError(t, err) + + assert.Empty(t, req.Header.Values("X-Tags")) + }) + + t.Run("non-nil header array param is sent", func(t *testing.T) { + params := GetTestParams{ + XTags: []string{"a", "b"}, + } + + req, err := NewGetTestRequest("https://localhost", ¶ms) + require.NoError(t, err) + + assert.NotEmpty(t, req.Header.Values("X-Tags")) + }) + + t.Run("nil cookie array param is not sent", func(t *testing.T) { + params := GetTestParams{ + Tags: nil, + } + + req, err := NewGetTestRequest("https://localhost", ¶ms) + require.NoError(t, err) + + assert.Empty(t, req.Cookies()) + }) + + t.Run("non-nil cookie array param is sent", func(t *testing.T) { + params := GetTestParams{ + Tags: []string{"a", "b"}, + } + + req, err := NewGetTestRequest("https://localhost", ¶ms) + require.NoError(t, err) + + cookies := req.Cookies() + require.Len(t, cookies, 1) + assert.Equal(t, "tags", cookies[0].Name) + }) +} diff --git a/internal/test/issues/issue-2238/openapi.yaml b/internal/test/issues/issue-2238/openapi.yaml new file mode 100644 index 0000000000..4e026542ac --- /dev/null +++ b/internal/test/issues/issue-2238/openapi.yaml @@ -0,0 +1,22 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Issue 2238 +paths: + /test: + get: + parameters: + - name: X-Tags + in: header + schema: + type: array + items: + type: string + required: false + - name: tags + in: cookie + schema: + type: array + items: + type: string + required: false diff --git a/internal/test/issues/issue-312/config.yaml b/internal/test/issues/issue-312/config.yaml index a0db7e9783..39cb190515 100644 --- a/internal/test/issues/issue-312/config.yaml +++ b/internal/test/issues/issue-312/config.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: issue312 generate: echo-server: true diff --git a/internal/test/issues/issue-312/doc.go b/internal/test/issues/issue-312/doc.go index 8eb982df90..768a4595b8 100644 --- a/internal/test/issues/issue-312/doc.go +++ b/internal/test/issues/issue-312/doc.go @@ -1,3 +1,3 @@ package issue312 -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml spec.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/issues/issue-312/issue.gen.go b/internal/test/issues/issue-312/issue.gen.go index c077475b24..1686dac248 100644 --- a/internal/test/issues/issue-312/issue.gen.go +++ b/internal/test/issues/issue-312/issue.gen.go @@ -1,6 +1,6 @@ // Package issue312 provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package issue312 import ( @@ -16,9 +16,9 @@ import ( "path" "strings" - "github.com/deepmap/oapi-codegen/pkg/runtime" "github.com/getkin/kin-openapi/openapi3" "github.com/labstack/echo/v4" + "github.com/oapi-codegen/runtime" ) // Error defines model for Error. @@ -121,7 +121,7 @@ type ClientInterface interface { // GetPet request GetPet(ctx context.Context, petId string, reqEditors ...RequestEditorFn) (*http.Response, error) - // ValidatePets request with any body + // ValidatePetsWithBody request with any body ValidatePetsWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) ValidatePets(ctx context.Context, body ValidatePetsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -169,7 +169,7 @@ func NewGetPetRequest(server string, petId string) (*http.Request, error) { var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "petId", runtime.ParamLocationPath, petId) + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "petId", petId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) if err != nil { return nil, err } @@ -280,10 +280,10 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { - // GetPet request + // GetPetWithResponse request GetPetWithResponse(ctx context.Context, petId string, reqEditors ...RequestEditorFn) (*GetPetResponse, error) - // ValidatePets request with any body + // ValidatePetsWithBodyWithResponse request with any body ValidatePetsWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ValidatePetsResponse, error) ValidatePetsWithResponse(ctx context.Context, body ValidatePetsJSONRequestBody, reqEditors ...RequestEditorFn) (*ValidatePetsResponse, error) @@ -440,12 +440,12 @@ func (w *ServerInterfaceWrapper) GetPet(ctx echo.Context) error { // ------------- Path parameter "petId" ------------- var petId string - err = runtime.BindStyledParameterWithLocation("simple", false, "petId", runtime.ParamLocationPath, ctx.Param("petId"), &petId) + err = runtime.BindStyledParameterWithOptions("simple", "petId", ctx.Param("petId"), &petId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter petId: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetPet(ctx, petId) return err } @@ -454,7 +454,7 @@ func (w *ServerInterfaceWrapper) GetPet(ctx echo.Context) error { func (w *ServerInterfaceWrapper) ValidatePets(ctx echo.Context) error { var err error - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.ValidatePets(ctx) return err } @@ -511,16 +511,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -538,7 +538,7 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } @@ -552,12 +552,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/internal/test/issues/issue-52/config.yaml b/internal/test/issues/issue-52/config.yaml index 8ea198ff2d..1277b9c971 100644 --- a/internal/test/issues/issue-52/config.yaml +++ b/internal/test/issues/issue-52/config.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: issue52 generate: echo-server: true diff --git a/internal/test/issues/issue-52/doc.go b/internal/test/issues/issue-52/doc.go index f29e0e9f83..3d239ebdcf 100644 --- a/internal/test/issues/issue-52/doc.go +++ b/internal/test/issues/issue-52/doc.go @@ -1,3 +1,3 @@ package issue52 -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml spec.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/issues/issue-52/issue.gen.go b/internal/test/issues/issue-52/issue.gen.go index d3f1a236da..f024999d6a 100644 --- a/internal/test/issues/issue-52/issue.gen.go +++ b/internal/test/issues/issue-52/issue.gen.go @@ -1,6 +1,6 @@ // Package issue52 provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package issue52 import ( @@ -193,7 +193,7 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { - // ExampleGet request + // ExampleGetWithResponse request ExampleGetWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ExampleGetResponse, error) } @@ -270,7 +270,7 @@ type ServerInterfaceWrapper struct { func (w *ServerInterfaceWrapper) ExampleGet(ctx echo.Context) error { var err error - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.ExampleGet(ctx) return err } @@ -323,16 +323,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -350,7 +350,7 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } @@ -364,12 +364,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/internal/test/issues/issue-52/issue_test.go b/internal/test/issues/issue-52/issue_test.go index 0d90bf68b4..0288c2aead 100644 --- a/internal/test/issues/issue-52/issue_test.go +++ b/internal/test/issues/issue-52/issue_test.go @@ -4,8 +4,8 @@ import ( _ "embed" "testing" - "github.com/deepmap/oapi-codegen/pkg/codegen" "github.com/getkin/kin-openapi/openapi3" + "github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen" "github.com/stretchr/testify/require" ) diff --git a/internal/test/issues/issue-579/gen.go b/internal/test/issues/issue-579/gen.go index d34774f7ea..7b198d0cdf 100644 --- a/internal/test/issues/issue-579/gen.go +++ b/internal/test/issues/issue-579/gen.go @@ -1,3 +1,3 @@ package issue579 -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=issue579 --generate=types,skip-prune --alias-types -o issue.gen.go spec.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --package=issue579 --generate=types,skip-prune --alias-types -o issue.gen.go spec.yaml diff --git a/internal/test/issues/issue-579/issue.gen.go b/internal/test/issues/issue-579/issue.gen.go index b7284ffee8..54326646d9 100644 --- a/internal/test/issues/issue-579/issue.gen.go +++ b/internal/test/issues/issue-579/issue.gen.go @@ -1,10 +1,10 @@ // Package issue579 provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package issue579 import ( - openapi_types "github.com/deepmap/oapi-codegen/pkg/types" + openapi_types "github.com/oapi-codegen/runtime/types" ) // AliasedDate defines model for AliasedDate. diff --git a/internal/test/issues/issue-832/config.yaml b/internal/test/issues/issue-832/config.yaml index e1eb4e5f6a..446d7a14e6 100644 --- a/internal/test/issues/issue-832/config.yaml +++ b/internal/test/issues/issue-832/config.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: issue_832 generate: models: true diff --git a/internal/test/issues/issue-832/generate.go b/internal/test/issues/issue-832/generate.go index 877007acbb..79b06c7131 100644 --- a/internal/test/issues/issue-832/generate.go +++ b/internal/test/issues/issue-832/generate.go @@ -1,3 +1,3 @@ package issue_832 -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml spec.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/issues/issue-832/issue.gen.go b/internal/test/issues/issue-832/issue.gen.go index 6bad2fbda1..4a5aa56a3d 100644 --- a/internal/test/issues/issue-832/issue.gen.go +++ b/internal/test/issues/issue-832/issue.gen.go @@ -1,6 +1,6 @@ // Package issue_832 provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package issue_832 import ( @@ -23,6 +23,22 @@ const ( Two Document_Status = "two" ) +// Valid indicates whether the value is a known member of the Document_Status enum. +func (e Document_Status) Valid() bool { + switch e { + case Four: + return true + case One: + return true + case Three: + return true + case Two: + return true + default: + return false + } +} + // Document defines model for Document. type Document struct { Name *string `json:"name,omitempty"` @@ -53,16 +69,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -80,7 +96,7 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } @@ -94,12 +110,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/internal/test/issues/issue-936/api.gen.go b/internal/test/issues/issue-936/api.gen.go new file mode 100644 index 0000000000..6b7c0cf220 --- /dev/null +++ b/internal/test/issues/issue-936/api.gen.go @@ -0,0 +1,432 @@ +// Package issue936 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue936 + +import ( + "encoding/json" + + "github.com/oapi-codegen/runtime" +) + +// FilterPredicate defines model for FilterPredicate. +type FilterPredicate struct { + union json.RawMessage +} + +// FilterPredicate1 defines model for . +type FilterPredicate1 = []FilterPredicate + +// FilterPredicateOp defines model for FilterPredicateOp. +type FilterPredicateOp struct { + Any *FilterPredicateOp_Any `json:"$any,omitempty"` + None *FilterPredicateOp_None `json:"$none,omitempty"` +} + +// FilterPredicateOpAny0 defines model for . +type FilterPredicateOpAny0 = []FilterPredicate + +// FilterPredicateOp_Any defines model for FilterPredicateOp.Any. +type FilterPredicateOp_Any struct { + union json.RawMessage +} + +// FilterPredicateOpNone1 defines model for . +type FilterPredicateOpNone1 = []FilterPredicate + +// FilterPredicateOp_None defines model for FilterPredicateOp.None. +type FilterPredicateOp_None struct { + union json.RawMessage +} + +// FilterPredicateRangeOp defines model for FilterPredicateRangeOp. +type FilterPredicateRangeOp struct { + Lt *FilterRangeValue `json:"$lt,omitempty"` +} + +// FilterRangeValue defines model for FilterRangeValue. +type FilterRangeValue struct { + union json.RawMessage +} + +// FilterRangeValue0 defines model for . +type FilterRangeValue0 = float32 + +// FilterRangeValue1 defines model for . +type FilterRangeValue1 = string + +// FilterValue defines model for FilterValue. +type FilterValue struct { + union json.RawMessage +} + +// FilterValue0 defines model for . +type FilterValue0 = float32 + +// FilterValue1 defines model for . +type FilterValue1 = string + +// FilterValue2 defines model for . +type FilterValue2 = bool + +// AsFilterValue returns the union data inside the FilterPredicate as a FilterValue +func (t FilterPredicate) AsFilterValue() (FilterValue, error) { + var body FilterValue + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromFilterValue overwrites any union data inside the FilterPredicate as the provided FilterValue +func (t *FilterPredicate) FromFilterValue(v FilterValue) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeFilterValue performs a merge with any union data inside the FilterPredicate, using the provided FilterValue +func (t *FilterPredicate) MergeFilterValue(v FilterValue) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsFilterPredicate1 returns the union data inside the FilterPredicate as a FilterPredicate1 +func (t FilterPredicate) AsFilterPredicate1() (FilterPredicate1, error) { + var body FilterPredicate1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromFilterPredicate1 overwrites any union data inside the FilterPredicate as the provided FilterPredicate1 +func (t *FilterPredicate) FromFilterPredicate1(v FilterPredicate1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeFilterPredicate1 performs a merge with any union data inside the FilterPredicate, using the provided FilterPredicate1 +func (t *FilterPredicate) MergeFilterPredicate1(v FilterPredicate1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsFilterPredicateOp returns the union data inside the FilterPredicate as a FilterPredicateOp +func (t FilterPredicate) AsFilterPredicateOp() (FilterPredicateOp, error) { + var body FilterPredicateOp + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromFilterPredicateOp overwrites any union data inside the FilterPredicate as the provided FilterPredicateOp +func (t *FilterPredicate) FromFilterPredicateOp(v FilterPredicateOp) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeFilterPredicateOp performs a merge with any union data inside the FilterPredicate, using the provided FilterPredicateOp +func (t *FilterPredicate) MergeFilterPredicateOp(v FilterPredicateOp) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsFilterPredicateRangeOp returns the union data inside the FilterPredicate as a FilterPredicateRangeOp +func (t FilterPredicate) AsFilterPredicateRangeOp() (FilterPredicateRangeOp, error) { + var body FilterPredicateRangeOp + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromFilterPredicateRangeOp overwrites any union data inside the FilterPredicate as the provided FilterPredicateRangeOp +func (t *FilterPredicate) FromFilterPredicateRangeOp(v FilterPredicateRangeOp) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeFilterPredicateRangeOp performs a merge with any union data inside the FilterPredicate, using the provided FilterPredicateRangeOp +func (t *FilterPredicate) MergeFilterPredicateRangeOp(v FilterPredicateRangeOp) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t FilterPredicate) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *FilterPredicate) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsFilterPredicateOpAny0 returns the union data inside the FilterPredicateOp_Any as a FilterPredicateOpAny0 +func (t FilterPredicateOp_Any) AsFilterPredicateOpAny0() (FilterPredicateOpAny0, error) { + var body FilterPredicateOpAny0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromFilterPredicateOpAny0 overwrites any union data inside the FilterPredicateOp_Any as the provided FilterPredicateOpAny0 +func (t *FilterPredicateOp_Any) FromFilterPredicateOpAny0(v FilterPredicateOpAny0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeFilterPredicateOpAny0 performs a merge with any union data inside the FilterPredicateOp_Any, using the provided FilterPredicateOpAny0 +func (t *FilterPredicateOp_Any) MergeFilterPredicateOpAny0(v FilterPredicateOpAny0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t FilterPredicateOp_Any) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *FilterPredicateOp_Any) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsFilterPredicate returns the union data inside the FilterPredicateOp_None as a FilterPredicate +func (t FilterPredicateOp_None) AsFilterPredicate() (FilterPredicate, error) { + var body FilterPredicate + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromFilterPredicate overwrites any union data inside the FilterPredicateOp_None as the provided FilterPredicate +func (t *FilterPredicateOp_None) FromFilterPredicate(v FilterPredicate) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeFilterPredicate performs a merge with any union data inside the FilterPredicateOp_None, using the provided FilterPredicate +func (t *FilterPredicateOp_None) MergeFilterPredicate(v FilterPredicate) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsFilterPredicateOpNone1 returns the union data inside the FilterPredicateOp_None as a FilterPredicateOpNone1 +func (t FilterPredicateOp_None) AsFilterPredicateOpNone1() (FilterPredicateOpNone1, error) { + var body FilterPredicateOpNone1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromFilterPredicateOpNone1 overwrites any union data inside the FilterPredicateOp_None as the provided FilterPredicateOpNone1 +func (t *FilterPredicateOp_None) FromFilterPredicateOpNone1(v FilterPredicateOpNone1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeFilterPredicateOpNone1 performs a merge with any union data inside the FilterPredicateOp_None, using the provided FilterPredicateOpNone1 +func (t *FilterPredicateOp_None) MergeFilterPredicateOpNone1(v FilterPredicateOpNone1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t FilterPredicateOp_None) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *FilterPredicateOp_None) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsFilterRangeValue0 returns the union data inside the FilterRangeValue as a FilterRangeValue0 +func (t FilterRangeValue) AsFilterRangeValue0() (FilterRangeValue0, error) { + var body FilterRangeValue0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromFilterRangeValue0 overwrites any union data inside the FilterRangeValue as the provided FilterRangeValue0 +func (t *FilterRangeValue) FromFilterRangeValue0(v FilterRangeValue0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeFilterRangeValue0 performs a merge with any union data inside the FilterRangeValue, using the provided FilterRangeValue0 +func (t *FilterRangeValue) MergeFilterRangeValue0(v FilterRangeValue0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsFilterRangeValue1 returns the union data inside the FilterRangeValue as a FilterRangeValue1 +func (t FilterRangeValue) AsFilterRangeValue1() (FilterRangeValue1, error) { + var body FilterRangeValue1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromFilterRangeValue1 overwrites any union data inside the FilterRangeValue as the provided FilterRangeValue1 +func (t *FilterRangeValue) FromFilterRangeValue1(v FilterRangeValue1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeFilterRangeValue1 performs a merge with any union data inside the FilterRangeValue, using the provided FilterRangeValue1 +func (t *FilterRangeValue) MergeFilterRangeValue1(v FilterRangeValue1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t FilterRangeValue) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *FilterRangeValue) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsFilterValue0 returns the union data inside the FilterValue as a FilterValue0 +func (t FilterValue) AsFilterValue0() (FilterValue0, error) { + var body FilterValue0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromFilterValue0 overwrites any union data inside the FilterValue as the provided FilterValue0 +func (t *FilterValue) FromFilterValue0(v FilterValue0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeFilterValue0 performs a merge with any union data inside the FilterValue, using the provided FilterValue0 +func (t *FilterValue) MergeFilterValue0(v FilterValue0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsFilterValue1 returns the union data inside the FilterValue as a FilterValue1 +func (t FilterValue) AsFilterValue1() (FilterValue1, error) { + var body FilterValue1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromFilterValue1 overwrites any union data inside the FilterValue as the provided FilterValue1 +func (t *FilterValue) FromFilterValue1(v FilterValue1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeFilterValue1 performs a merge with any union data inside the FilterValue, using the provided FilterValue1 +func (t *FilterValue) MergeFilterValue1(v FilterValue1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsFilterValue2 returns the union data inside the FilterValue as a FilterValue2 +func (t FilterValue) AsFilterValue2() (FilterValue2, error) { + var body FilterValue2 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromFilterValue2 overwrites any union data inside the FilterValue as the provided FilterValue2 +func (t *FilterValue) FromFilterValue2(v FilterValue2) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeFilterValue2 performs a merge with any union data inside the FilterValue, using the provided FilterValue2 +func (t *FilterValue) MergeFilterValue2(v FilterValue2) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t FilterValue) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *FilterValue) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} diff --git a/internal/test/issues/issue-936/gen.go b/internal/test/issues/issue-936/gen.go new file mode 100644 index 0000000000..f1e62f42f1 --- /dev/null +++ b/internal/test/issues/issue-936/gen.go @@ -0,0 +1,3 @@ +package issue936 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=server.config.yaml spec.yaml diff --git a/internal/test/issues/issue-936/server.config.yaml b/internal/test/issues/issue-936/server.config.yaml new file mode 100644 index 0000000000..76b85471ab --- /dev/null +++ b/internal/test/issues/issue-936/server.config.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: issue936 +output: api.gen.go +generate: + models: true +compatibility: + circular-reference-limit: 4 diff --git a/internal/test/issues/issue-936/spec.yaml b/internal/test/issues/issue-936/spec.yaml new file mode 100644 index 0000000000..8d9a7cf1d8 --- /dev/null +++ b/internal/test/issues/issue-936/spec.yaml @@ -0,0 +1,60 @@ +# Via https://github.com/getkin/kin-openapi/blob/b31a4bb2dcd523b1477da8854f0f75859cd314b3/openapi3/testdata/recursiveRef/issue615.yml +openapi: "3.0.3" +info: + title: Deep recursive cyclic refs example + version: "1.0" +paths: + /foo: {} +components: + schemas: + FilterColumnIncludes: + type: object + properties: + $includes: + $ref: '#/components/schemas/FilterPredicate' + additionalProperties: false + maxProperties: 1 + minProperties: 1 + FilterPredicate: + oneOf: + - $ref: '#/components/schemas/FilterValue' + - type: array + items: + $ref: '#/components/schemas/FilterPredicate' + minLength: 1 + - $ref: '#/components/schemas/FilterPredicateOp' + - $ref: '#/components/schemas/FilterPredicateRangeOp' + FilterPredicateOp: + type: object + properties: + $any: + oneOf: + - type: array + items: + $ref: '#/components/schemas/FilterPredicate' + $none: + oneOf: + - $ref: '#/components/schemas/FilterPredicate' + - type: array + items: + $ref: '#/components/schemas/FilterPredicate' + additionalProperties: false + maxProperties: 1 + minProperties: 1 + FilterPredicateRangeOp: + type: object + properties: + $lt: + $ref: '#/components/schemas/FilterRangeValue' + additionalProperties: false + maxProperties: 2 + minProperties: 2 + FilterRangeValue: + oneOf: + - type: number + - type: string + FilterValue: + oneOf: + - type: number + - type: string + - type: boolean diff --git a/internal/test/issues/issue-grab_import_names/config.yaml b/internal/test/issues/issue-grab_import_names/config.yaml index 3248a4893f..85e3f15e69 100644 --- a/internal/test/issues/issue-grab_import_names/config.yaml +++ b/internal/test/issues/issue-grab_import_names/config.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: grabimportnames generate: echo-server: true diff --git a/internal/test/issues/issue-grab_import_names/doc.go b/internal/test/issues/issue-grab_import_names/doc.go index 5c73385b07..e986569fcb 100644 --- a/internal/test/issues/issue-grab_import_names/doc.go +++ b/internal/test/issues/issue-grab_import_names/doc.go @@ -1,3 +1,3 @@ package grabimportnames -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml spec.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/issues/issue-grab_import_names/issue.gen.go b/internal/test/issues/issue-grab_import_names/issue.gen.go index b9904fcde1..99bfcd9548 100644 --- a/internal/test/issues/issue-grab_import_names/issue.gen.go +++ b/internal/test/issues/issue-grab_import_names/issue.gen.go @@ -1,6 +1,6 @@ // Package grabimportnames provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package grabimportnames import ( @@ -16,9 +16,9 @@ import ( "path" "strings" - "github.com/deepmap/oapi-codegen/pkg/runtime" "github.com/getkin/kin-openapi/openapi3" "github.com/labstack/echo/v4" + "github.com/oapi-codegen/runtime" ) // GetFooParams defines parameters for GetFoo. @@ -143,26 +143,30 @@ func NewGetFooRequest(server string, params *GetFooParams) (*http.Request, error return nil, err } - if params.Foo != nil { - var headerParam0 string + if params != nil { - headerParam0, err = runtime.StyleParamWithLocation("simple", false, "Foo", runtime.ParamLocationHeader, *params.Foo) - if err != nil { - return nil, err + if params.Foo != nil { + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "Foo", *params.Foo, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("Foo", headerParam0) } - req.Header.Set("Foo", headerParam0) - } + if params.Bar != nil { + var headerParam1 string - if params.Bar != nil { - var headerParam1 string + headerParam1, err = runtime.StyleParamWithOptions("simple", false, "Bar", *params.Bar, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } - headerParam1, err = runtime.StyleParamWithLocation("simple", false, "Bar", runtime.ParamLocationHeader, *params.Bar) - if err != nil { - return nil, err + req.Header.Set("Bar", headerParam1) } - req.Header.Set("Bar", headerParam1) } return req, nil @@ -211,7 +215,7 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { - // GetFoo request + // GetFooWithResponse request GetFooWithResponse(ctx context.Context, params *GetFooParams, reqEditors ...RequestEditorFn) (*GetFooResponse, error) } @@ -300,7 +304,7 @@ func (w *ServerInterfaceWrapper) GetFoo(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for Foo, got %d", n)) } - err = runtime.BindStyledParameterWithLocation("simple", false, "Foo", runtime.ParamLocationHeader, valueList[0], &Foo) + err = runtime.BindStyledParameterWithOptions("simple", "Foo", valueList[0], &Foo, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false, Type: "string", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter Foo: %s", err)) } @@ -315,7 +319,7 @@ func (w *ServerInterfaceWrapper) GetFoo(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for Bar, got %d", n)) } - err = runtime.BindStyledParameterWithLocation("simple", false, "Bar", runtime.ParamLocationHeader, valueList[0], &Bar) + err = runtime.BindStyledParameterWithOptions("simple", "Bar", valueList[0], &Bar, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false, Type: "string", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter Bar: %s", err)) } @@ -323,7 +327,7 @@ func (w *ServerInterfaceWrapper) GetFoo(ctx echo.Context) error { params.Bar = &Bar } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetFoo(ctx, params) return err } @@ -376,16 +380,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -403,7 +407,7 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } @@ -417,12 +421,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/internal/test/issues/issue-grab_import_names/issue_test.go b/internal/test/issues/issue-grab_import_names/issue_test.go index 8dffcd22b2..7d299fa22a 100644 --- a/internal/test/issues/issue-grab_import_names/issue_test.go +++ b/internal/test/issues/issue-grab_import_names/issue_test.go @@ -3,8 +3,8 @@ package grabimportnames import ( "testing" - "github.com/deepmap/oapi-codegen/pkg/codegen" "github.com/getkin/kin-openapi/openapi3" + "github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen" "github.com/stretchr/testify/require" ) diff --git a/internal/test/issues/issue-head-digit-of-httpheader/config.yaml b/internal/test/issues/issue-head-digit-of-httpheader/config.yaml index 1f7ce9d390..37457c892a 100644 --- a/internal/test/issues/issue-head-digit-of-httpheader/config.yaml +++ b/internal/test/issues/issue-head-digit-of-httpheader/config.yaml @@ -1,4 +1,5 @@ --- +# yaml-language-server: $schema=../../../../configuration-schema.json package: headdigitofhttpheader generate: strict-server: true diff --git a/internal/test/issues/issue-head-digit-of-httpheader/doc.go b/internal/test/issues/issue-head-digit-of-httpheader/doc.go index 0c4cfc13a0..63904053bf 100644 --- a/internal/test/issues/issue-head-digit-of-httpheader/doc.go +++ b/internal/test/issues/issue-head-digit-of-httpheader/doc.go @@ -1,3 +1,3 @@ package headdigitofhttpheader -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml spec.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/issues/issue-head-digit-of-httpheader/issue.gen.go b/internal/test/issues/issue-head-digit-of-httpheader/issue.gen.go index 703c262af6..6eadcef68e 100644 --- a/internal/test/issues/issue-head-digit-of-httpheader/issue.gen.go +++ b/internal/test/issues/issue-head-digit-of-httpheader/issue.gen.go @@ -1,39 +1,11 @@ // Package headdigitofhttpheader provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package headdigitofhttpheader -import ( - "context" - "fmt" - "net/http" -) - type N200ResponseHeaders struct { N000Foo string } type N200Response struct { Headers N200ResponseHeaders } - -type GetFooRequestObject struct { -} - -type GetFooResponseObject interface { - VisitGetFooResponse(w http.ResponseWriter) error -} - -type GetFoo200Response = N200Response - -func (response GetFoo200Response) VisitGetFooResponse(w http.ResponseWriter) error { - w.Header().Set("000-foo", fmt.Sprint(response.Headers.N000Foo)) - w.WriteHeader(200) - return nil -} - -// StrictServerInterface represents all server handlers. -type StrictServerInterface interface { - - // (GET /foo) - GetFoo(ctx context.Context, request GetFooRequestObject) (GetFooResponseObject, error) -} diff --git a/internal/test/issues/issue-head-digit-of-operation-id/config.yaml b/internal/test/issues/issue-head-digit-of-operation-id/config.yaml index 5823c79ebb..9ac5a0a128 100644 --- a/internal/test/issues/issue-head-digit-of-operation-id/config.yaml +++ b/internal/test/issues/issue-head-digit-of-operation-id/config.yaml @@ -1,4 +1,5 @@ --- +# yaml-language-server: $schema=../../../../configuration-schema.json package: head_digit_of_operation_id generate: strict-server: true diff --git a/internal/test/issues/issue-head-digit-of-operation-id/doc.go b/internal/test/issues/issue-head-digit-of-operation-id/doc.go index 6faae82c83..fb08e63e86 100644 --- a/internal/test/issues/issue-head-digit-of-operation-id/doc.go +++ b/internal/test/issues/issue-head-digit-of-operation-id/doc.go @@ -1,3 +1,3 @@ package head_digit_of_operation_id -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml spec.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/issues/issue-head-digit-of-operation-id/issue.gen.go b/internal/test/issues/issue-head-digit-of-operation-id/issue.gen.go index 17607466a4..00c0844afc 100644 --- a/internal/test/issues/issue-head-digit-of-operation-id/issue.gen.go +++ b/internal/test/issues/issue-head-digit-of-operation-id/issue.gen.go @@ -1,23 +1,4 @@ // Package head_digit_of_operation_id provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package head_digit_of_operation_id - -import ( - "context" - "net/http" -) - -type N3GPPFooRequestObject struct { -} - -type N3GPPFooResponseObject interface { - VisitN3GPPFooResponse(w http.ResponseWriter) error -} - -// StrictServerInterface represents all server handlers. -type StrictServerInterface interface { - - // (GET /3gpp/foo) - N3GPPFoo(ctx context.Context, request N3GPPFooRequestObject) (N3GPPFooResponseObject, error) -} diff --git a/internal/test/issues/issue-illegal_enum_names/config.yaml b/internal/test/issues/issue-illegal_enum_names/config.yaml index 9b426a0d54..e8bbe0e352 100644 --- a/internal/test/issues/issue-illegal_enum_names/config.yaml +++ b/internal/test/issues/issue-illegal_enum_names/config.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: illegalenumnames generate: echo-server: true diff --git a/internal/test/issues/issue-illegal_enum_names/doc.go b/internal/test/issues/issue-illegal_enum_names/doc.go index de0c23ffdb..d8fb076b49 100644 --- a/internal/test/issues/issue-illegal_enum_names/doc.go +++ b/internal/test/issues/issue-illegal_enum_names/doc.go @@ -1,3 +1,3 @@ package illegalenumnames -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml spec.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/issues/issue-illegal_enum_names/issue.gen.go b/internal/test/issues/issue-illegal_enum_names/issue.gen.go index 3f0d8e0803..0c29ab71eb 100644 --- a/internal/test/issues/issue-illegal_enum_names/issue.gen.go +++ b/internal/test/issues/issue-illegal_enum_names/issue.gen.go @@ -1,6 +1,6 @@ // Package illegalenumnames provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package illegalenumnames import ( @@ -22,18 +22,46 @@ import ( // Defines values for Bar. const ( - BarBar Bar = "Bar" - BarEmpty Bar = "" - BarFoo Bar = "Foo" - BarFoo1 Bar = " Foo" - BarFoo2 Bar = " Foo " - BarFoo3 Bar = "_Foo_" - BarFooBar Bar = "Foo Bar" - BarFooBar1 Bar = "Foo-Bar" - BarN1 Bar = "1" - BarN1Foo Bar = "1Foo" + BarBar Bar = "Bar" + BarEmpty Bar = "" + BarFoo Bar = "Foo" + BarFoo1 Bar = " Foo" + BarFoo2 Bar = " Foo " + BarFooBar Bar = "Foo Bar" + BarFooBar1 Bar = "Foo-Bar" + BarN1 Bar = "1" + BarN1Foo Bar = "1Foo" + BarUnderscoreFoo Bar = "_Foo_" ) +// Valid indicates whether the value is a known member of the Bar enum. +func (e Bar) Valid() bool { + switch e { + case BarBar: + return true + case BarEmpty: + return true + case BarFoo: + return true + case BarFoo1: + return true + case BarFoo2: + return true + case BarFooBar: + return true + case BarFooBar1: + return true + case BarN1: + return true + case BarN1Foo: + return true + case BarUnderscoreFoo: + return true + default: + return false + } +} + // Bar defines model for Bar. type Bar string @@ -196,7 +224,7 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { - // GetFoo request + // GetFooWithResponse request GetFooWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetFooResponse, error) } @@ -273,7 +301,7 @@ type ServerInterfaceWrapper struct { func (w *ServerInterfaceWrapper) GetFoo(ctx echo.Context) error { var err error - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetFoo(ctx) return err } @@ -325,16 +353,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -352,7 +380,7 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } @@ -366,12 +394,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/internal/test/issues/issue-illegal_enum_names/issue_test.go b/internal/test/issues/issue-illegal_enum_names/issue_test.go index 3df57c8eea..77d4eb416c 100644 --- a/internal/test/issues/issue-illegal_enum_names/issue_test.go +++ b/internal/test/issues/issue-illegal_enum_names/issue_test.go @@ -6,8 +6,8 @@ import ( "go/token" "testing" - "github.com/deepmap/oapi-codegen/pkg/codegen" "github.com/getkin/kin-openapi/openapi3" + "github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen" "github.com/stretchr/testify/require" ) @@ -54,6 +54,6 @@ func TestIllegalEnumNames(t *testing.T) { require.Equal(t, `"1Foo"`, constDefs["BarN1Foo"]) require.Equal(t, `" Foo"`, constDefs["BarFoo1"]) require.Equal(t, `" Foo "`, constDefs["BarFoo2"]) - require.Equal(t, `"_Foo_"`, constDefs["BarFoo3"]) + require.Equal(t, `"_Foo_"`, constDefs["BarUnderscoreFoo"]) require.Equal(t, `"1"`, constDefs["BarN1"]) } diff --git a/internal/test/issues/issue-removed-external-ref/config.base.yaml b/internal/test/issues/issue-removed-external-ref/config.base.yaml index 17d4d2f085..14faed834b 100644 --- a/internal/test/issues/issue-removed-external-ref/config.base.yaml +++ b/internal/test/issues/issue-removed-external-ref/config.base.yaml @@ -1,11 +1,12 @@ --- +# yaml-language-server: $schema=../../../../configuration-schema.json package: spec_base generate: chi-server: true strict-server: true models: true import-mapping: - spec-ext.yaml: "github.com/deepmap/oapi-codegen/internal/test/issues/issue-removed-external-ref/gen/spec_ext" + spec-ext.yaml: "github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-removed-external-ref/gen/spec_ext" output: gen/spec_base/issue.gen.go output-options: skip-prune: true diff --git a/internal/test/issues/issue-removed-external-ref/config.ext.yaml b/internal/test/issues/issue-removed-external-ref/config.ext.yaml index 9d0478065a..efb166ed2f 100644 --- a/internal/test/issues/issue-removed-external-ref/config.ext.yaml +++ b/internal/test/issues/issue-removed-external-ref/config.ext.yaml @@ -1,4 +1,5 @@ --- +# yaml-language-server: $schema=../../../../configuration-schema.json package: spec_ext generate: chi-server: true diff --git a/internal/test/issues/issue-removed-external-ref/doc.go b/internal/test/issues/issue-removed-external-ref/doc.go index 7cb5ab0d8b..6af4d981ff 100644 --- a/internal/test/issues/issue-removed-external-ref/doc.go +++ b/internal/test/issues/issue-removed-external-ref/doc.go @@ -1,4 +1,4 @@ package head_digit_of_httpheader -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.ext.yaml spec-ext.yaml -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.base.yaml spec-base.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.ext.yaml spec-ext.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.base.yaml spec-base.yaml diff --git a/internal/test/issues/issue-removed-external-ref/gen/spec_base/issue.gen.go b/internal/test/issues/issue-removed-external-ref/gen/spec_base/issue.gen.go index 09e8df32f5..1a1c761002 100644 --- a/internal/test/issues/issue-removed-external-ref/gen/spec_base/issue.gen.go +++ b/internal/test/issues/issue-removed-external-ref/gen/spec_base/issue.gen.go @@ -1,6 +1,6 @@ // Package spec_base provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package spec_base import ( @@ -9,8 +9,9 @@ import ( "fmt" "net/http" - externalRef0 "github.com/deepmap/oapi-codegen/internal/test/issues/issue-removed-external-ref/gen/spec_ext" "github.com/go-chi/chi/v5" + externalRef0 "github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-removed-external-ref/gen/spec_ext" + strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp" ) // DirectBar defines model for DirectBar. @@ -33,6 +34,20 @@ type ServerInterface interface { PostNoTrouble(w http.ResponseWriter, r *http.Request) } +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct{} + +// (POST /invalidExtRefTrouble) +func (_ Unimplemented) PostInvalidExtRefTrouble(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// (POST /noTrouble) +func (_ Unimplemented) PostNoTrouble(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + // ServerInterfaceWrapper converts contexts to parameters. type ServerInterfaceWrapper struct { Handler ServerInterface @@ -44,32 +59,30 @@ type MiddlewareFunc func(http.Handler) http.Handler // PostInvalidExtRefTrouble operation middleware func (siw *ServerInterfaceWrapper) PostInvalidExtRefTrouble(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.PostInvalidExtRefTrouble(w, r) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // PostNoTrouble operation middleware func (siw *ServerInterfaceWrapper) PostNoTrouble(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.PostNoTrouble(w, r) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } type UnescapedCookieParamError struct { @@ -85,16 +98,16 @@ func (e *UnescapedCookieParamError) Unwrap() error { return e.Err } -type UnmarshallingParamError struct { +type UnmarshalingParamError struct { ParamName string Err error } -func (e *UnmarshallingParamError) Error() string { - return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) } -func (e *UnmarshallingParamError) Unwrap() error { +func (e *UnmarshalingParamError) Unwrap() error { return e.Err } @@ -202,9 +215,7 @@ type PostInvalidExtRefTroubleResponseObject interface { VisitPostInvalidExtRefTroubleResponse(w http.ResponseWriter) error } -type PostInvalidExtRefTrouble300JSONResponse struct { - externalRef0.PascalJSONResponse -} +type PostInvalidExtRefTrouble300JSONResponse struct{ externalRef0.Pascal } func (response PostInvalidExtRefTrouble300JSONResponse) VisitPostInvalidExtRefTroubleResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") @@ -244,9 +255,8 @@ type StrictServerInterface interface { PostNoTrouble(ctx context.Context, request PostNoTroubleRequestObject) (PostNoTroubleResponseObject, error) } -type StrictHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, args interface{}) (interface{}, error) - -type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc +type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc +type StrictMiddlewareFunc = strictnethttp.StrictHTTPMiddlewareFunc type StrictHTTPServerOptions struct { RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) @@ -294,7 +304,7 @@ func (sh *strictHandler) PostInvalidExtRefTrouble(w http.ResponseWriter, r *http sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) } } @@ -318,6 +328,6 @@ func (sh *strictHandler) PostNoTrouble(w http.ResponseWriter, r *http.Request) { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) } } diff --git a/internal/test/issues/issue-removed-external-ref/gen/spec_ext/issue.gen.go b/internal/test/issues/issue-removed-external-ref/gen/spec_ext/issue.gen.go index 21222f879f..881f8be12b 100644 --- a/internal/test/issues/issue-removed-external-ref/gen/spec_ext/issue.gen.go +++ b/internal/test/issues/issue-removed-external-ref/gen/spec_ext/issue.gen.go @@ -1,14 +1,14 @@ // Package spec_ext provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package spec_ext import ( - "context" "fmt" "net/http" "github.com/go-chi/chi/v5" + strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp" ) // CamelSchema defines model for CamelSchema. @@ -35,6 +35,10 @@ type Pascal = PascalSchema type ServerInterface interface { } +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct{} + // ServerInterfaceWrapper converts contexts to parameters. type ServerInterfaceWrapper struct { Handler ServerInterface @@ -57,16 +61,16 @@ func (e *UnescapedCookieParamError) Unwrap() error { return e.Err } -type UnmarshallingParamError struct { +type UnmarshalingParamError struct { ParamName string Err error } -func (e *UnmarshallingParamError) Error() string { - return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) } -func (e *UnmarshallingParamError) Unwrap() error { +func (e *UnmarshalingParamError) Unwrap() error { return e.Err } @@ -161,9 +165,8 @@ type PascalJSONResponse PascalSchema type StrictServerInterface interface { } -type StrictHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, args interface{}) (interface{}, error) - -type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc +type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc +type StrictMiddlewareFunc = strictnethttp.StrictHTTPMiddlewareFunc type StrictHTTPServerOptions struct { RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) diff --git a/internal/test/issues/issue1469/config.yaml b/internal/test/issues/issue1469/config.yaml new file mode 100644 index 0000000000..0995b5f9fc --- /dev/null +++ b/internal/test/issues/issue1469/config.yaml @@ -0,0 +1,5 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: issue1469 +generate: + fiber-server: true +output: main.gen.go diff --git a/internal/test/issues/issue1469/doc.go b/internal/test/issues/issue1469/doc.go new file mode 100644 index 0000000000..d72f419f61 --- /dev/null +++ b/internal/test/issues/issue1469/doc.go @@ -0,0 +1,3 @@ +package issue1469 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/issues/issue1469/main.gen.go b/internal/test/issues/issue1469/main.gen.go new file mode 100644 index 0000000000..11f87b0c4f --- /dev/null +++ b/internal/test/issues/issue1469/main.gen.go @@ -0,0 +1,53 @@ +// Package issue1469 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue1469 + +import ( + "github.com/gofiber/fiber/v2" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /test) + Test(c *fiber.Ctx) error +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +type MiddlewareFunc fiber.Handler + +// Test operation middleware +func (siw *ServerInterfaceWrapper) Test(c *fiber.Ctx) error { + + return siw.Handler.Test(c) +} + +// FiberServerOptions provides options for the Fiber server. +type FiberServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router fiber.Router, si ServerInterface) { + RegisterHandlersWithOptions(router, si, FiberServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router fiber.Router, si ServerInterface, options FiberServerOptions) { + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + for _, m := range options.Middlewares { + router.Use(fiber.Handler(m)) + } + + router.Get(options.BaseURL+"/test", wrapper.Test) + +} diff --git a/internal/test/issues/issue1469/main_test.go b/internal/test/issues/issue1469/main_test.go new file mode 100644 index 0000000000..3af67244a9 --- /dev/null +++ b/internal/test/issues/issue1469/main_test.go @@ -0,0 +1,35 @@ +package issue1469 + +import ( + "testing" + + "github.com/gofiber/fiber/v2" + "github.com/stretchr/testify/assert" +) + +type impl struct{} + +// (GET /test) +func (i *impl) Test(c *fiber.Ctx) error { + panic("not implemented") // TODO: Implement +} + +func TestIssue1469(t *testing.T) { + server := &impl{} + + r := fiber.New() + + assert.NotPanics(t, func() { + RegisterHandlers(r, server) + }) + + assert.NotPanics(t, func() { + RegisterHandlersWithOptions(r, server, FiberServerOptions{ + Middlewares: []MiddlewareFunc{ + func(c *fiber.Ctx) error { + return nil + }, + }, + }) + }) +} diff --git a/internal/test/issues/issue1469/spec.yaml b/internal/test/issues/issue1469/spec.yaml new file mode 100644 index 0000000000..69a8a3e280 --- /dev/null +++ b/internal/test/issues/issue1469/spec.yaml @@ -0,0 +1,13 @@ +openapi: "3.0.1" +paths: + /test: + get: + operationId: test + requestBody: + content: + application/json: + schema: + type: object + responses: + 200: + description: good diff --git a/internal/test/issues/issue1561/config.yaml b/internal/test/issues/issue1561/config.yaml new file mode 100644 index 0000000000..e1df8f3b7e --- /dev/null +++ b/internal/test/issues/issue1561/config.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: issue1561 +generate: + models: true +output: issue1561.gen.go +output-options: + skip-prune: true + prefer-skip-optional-pointer-on-container-types: true diff --git a/internal/test/issues/issue1561/generate.go b/internal/test/issues/issue1561/generate.go new file mode 100644 index 0000000000..21cf1d305d --- /dev/null +++ b/internal/test/issues/issue1561/generate.go @@ -0,0 +1,3 @@ +package issue1561 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml openapi.yaml diff --git a/internal/test/issues/issue1561/issue1561.gen.go b/internal/test/issues/issue1561/issue1561.gen.go new file mode 100644 index 0000000000..f011e318c1 --- /dev/null +++ b/internal/test/issues/issue1561/issue1561.gen.go @@ -0,0 +1,21 @@ +// Package issue1561 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue1561 + +// Pong defines model for Pong. +type Pong struct { + Ping string `json:"ping"` +} + +// ResponseBody defines model for ResponseBody. +type ResponseBody struct { + AMap map[string]Pong `json:"a_map,omitempty"` + ASlice []Pong `json:"a_slice,omitempty"` + ASliceWithAdditionalProps []map[string]Pong `json:"a_slice_with_additional_props,omitempty"` + AdditionalProps map[string]Pong `json:"additional_props,omitempty"` + Bytes []byte `json:"bytes,omitempty"` + BytesWithOverride *[]byte `json:"bytes_with_override,omitempty"` + RequiredSlice []Pong `json:"required_slice"` + UnknownObject map[string]interface{} `json:"unknown_object,omitempty"` +} diff --git a/internal/test/issues/issue1561/issue1561_test.go b/internal/test/issues/issue1561/issue1561_test.go new file mode 100644 index 0000000000..0873793feb --- /dev/null +++ b/internal/test/issues/issue1561/issue1561_test.go @@ -0,0 +1,64 @@ +package issue1561 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestResponseBody_DoesNotHaveOptionalPointerToContainerTypes(t *testing.T) { + pong0 := Pong{ + Ping: "0th", + } + + pong1 := Pong{ + Ping: "1th", + } + + slice := []Pong{ + pong0, + pong1, + } + + m := map[string]Pong{ + "0": pong0, + "1": pong1, + } + + byteData := []byte("some bytes") + + body := ResponseBody{ + RequiredSlice: slice, + ASlice: slice, + AMap: m, + UnknownObject: map[string]any{}, + AdditionalProps: m, + ASliceWithAdditionalProps: []map[string]Pong{m}, + Bytes: byteData, + BytesWithOverride: &byteData, + } + + assert.NotNil(t, body.RequiredSlice) + assert.NotZero(t, body.RequiredSlice) + + assert.NotNil(t, body.ASlice) + assert.NotZero(t, body.ASlice) + + assert.NotNil(t, body.AMap) + assert.NotZero(t, body.AMap) + + assert.NotNil(t, body.UnknownObject) + assert.Empty(t, body.UnknownObject) + + assert.NotNil(t, body.AdditionalProps) + assert.NotZero(t, body.AdditionalProps) + + assert.NotNil(t, body.ASliceWithAdditionalProps) + assert.NotZero(t, body.ASliceWithAdditionalProps) + + assert.NotNil(t, body.Bytes) + assert.NotZero(t, body.Bytes) + + assert.NotNil(t, body.BytesWithOverride) + assert.NotZero(t, body.BytesWithOverride) +} diff --git a/internal/test/issues/issue1561/openapi.yaml b/internal/test/issues/issue1561/openapi.yaml new file mode 100644 index 0000000000..4e5cf771c0 --- /dev/null +++ b/internal/test/issues/issue1561/openapi.yaml @@ -0,0 +1,49 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: "When using `prefer-skip-optional-pointer-on-container-types`, container types do not have an 'optional pointer'" +paths: +components: + schemas: + ResponseBody: + type: object + required: + - required_slice + properties: + required_slice: + type: array + items: + $ref: '#/components/schemas/Pong' + a_slice: + type: array + items: + $ref: '#/components/schemas/Pong' + a_map: + additionalProperties: + $ref: '#/components/schemas/Pong' + unknown_object: + type: object + additional_props: + type: object + additionalProperties: + $ref: '#/components/schemas/Pong' + a_slice_with_additional_props: + type: array + items: + additionalProperties: + $ref: '#/components/schemas/Pong' + bytes: + type: string + format: byte + bytes_with_override: + type: string + format: byte + x-go-type-skip-optional-pointer: false + Pong: + type: object + required: + - ping + properties: + ping: + type: string + example: pong diff --git a/internal/test/issues/issue1767/config.yaml b/internal/test/issues/issue1767/config.yaml new file mode 100644 index 0000000000..c1bfe0b5b6 --- /dev/null +++ b/internal/test/issues/issue1767/config.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: issue1767 +generate: + models: true +output: issue1767.gen.go +output-options: + skip-prune: true diff --git a/internal/test/issues/issue1767/generate.go b/internal/test/issues/issue1767/generate.go new file mode 100644 index 0000000000..3f8e1ef572 --- /dev/null +++ b/internal/test/issues/issue1767/generate.go @@ -0,0 +1,3 @@ +package issue1767 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml openapi.yaml diff --git a/internal/test/issues/issue1767/issue1767.gen.go b/internal/test/issues/issue1767/issue1767.gen.go new file mode 100644 index 0000000000..1e7bdaddbb --- /dev/null +++ b/internal/test/issues/issue1767/issue1767.gen.go @@ -0,0 +1,17 @@ +// Package issue1767 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue1767 + +import ( + openapi_types "github.com/oapi-codegen/runtime/types" +) + +// Alarm Alarm Information +type Alarm struct { + // UnderscoreId Identifier of the Alarm. + UnderscoreId *openapi_types.UUID `json:"_id,omitempty"` + + // Id Identifier of the Alarm. + Id *openapi_types.UUID `json:"id,omitempty"` +} diff --git a/internal/test/issues/issue1767/openapi.yaml b/internal/test/issues/issue1767/openapi.yaml new file mode 100644 index 0000000000..5964395ed0 --- /dev/null +++ b/internal/test/issues/issue1767/openapi.yaml @@ -0,0 +1,20 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: "An underscore in the name of a field is remapped to `Underscore`" +paths: +components: + schemas: + Alarm: + description: | + Alarm Information + type: object + properties: + _id: + description: Identifier of the Alarm. + type: string + format: uuid + id: + description: Identifier of the Alarm. + type: string + format: uuid diff --git a/internal/test/issues/issue1825/config.yaml b/internal/test/issues/issue1825/config.yaml new file mode 100644 index 0000000000..2df08d4b71 --- /dev/null +++ b/internal/test/issues/issue1825/config.yaml @@ -0,0 +1,11 @@ +package: issue1825 +generate: + models: true + embedded-spec: true +import-mapping: + ../packageA/spec.yaml: github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue1825/packageA +output: issue1825.gen.go +output-options: + skip-prune: true + overlay: + path: overlay.yaml diff --git a/internal/test/issues/issue1825/doc.go b/internal/test/issues/issue1825/doc.go new file mode 100644 index 0000000000..664a3ae916 --- /dev/null +++ b/internal/test/issues/issue1825/doc.go @@ -0,0 +1,5 @@ +package issue1825 + +// We place the spec in a subdirectory, as this requires us to initialize the resolver kin-openapi's loader +// If this is not done, the generator would fail with an `encountered disallowed external reference` error. +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec/spec.yaml diff --git a/internal/test/issues/issue1825/issue1825.gen.go b/internal/test/issues/issue1825/issue1825.gen.go new file mode 100644 index 0000000000..679963f84c --- /dev/null +++ b/internal/test/issues/issue1825/issue1825.gen.go @@ -0,0 +1,110 @@ +// Package issue1825 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue1825 + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + externalRef0 "github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue1825/packageA" +) + +// Container defines model for Container. +type Container struct { + ObjectA *externalRef0.ObjectA `json:"object_a,omitempty"` + ObjectB *map[string]interface{} `json:"object_b,omitempty"` +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/4SP0U7DMAxF/8Xw2K2TeOvbxAfAH1Re6m2G1rESd2Ka8u/IWQcIkPaUWNf33OsLhDhp", + "FBLL0F0ghyNNWL/PUQxZKPmgKSolY6pS3L1RsB79/5hoDx08tN+gdqG0iuEdD7Tts1LoX6prC6W5AXb3", + "AF97pfxy4TCwcRQcX39UszRTA3ZWgm5Z97j/e/w5S3Aifxd/tsRy8GgPZ9nHKrKNrkIDJ0qZo1yHj1U8", + "URrxvELVkWm4EuZgc6LhJtbjlQSVoYOn9Wa9Ae9nR29QymcAAAD//5P9oGCQAQAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + for rawPath, rawFunc := range externalRef0.PathToRawSpec(path.Join(path.Dir(pathToFile), "../packageA/spec.yaml")) { + if _, ok := res[rawPath]; ok { + // it is not possible to compare functions in golang, so always overwrite the old value + } + res[rawPath] = rawFunc + } + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/issues/issue1825/object_b.json b/internal/test/issues/issue1825/object_b.json new file mode 100644 index 0000000000..cdd7fc12af --- /dev/null +++ b/internal/test/issues/issue1825/object_b.json @@ -0,0 +1,5 @@ +{ + "type": "object", + "properties": {}, + "additionalProperties": true +} diff --git a/internal/test/issues/issue1825/overlay.yaml b/internal/test/issues/issue1825/overlay.yaml new file mode 100644 index 0000000000..55011983fe --- /dev/null +++ b/internal/test/issues/issue1825/overlay.yaml @@ -0,0 +1,10 @@ +overlay: 1.0.0 +info: + title: Overlay example + version: 0.0.0 +actions: + - target: "$" + description: Add a property to the info dictionary + update: + info: + x-overlay-applied: structured-overlay diff --git a/internal/test/issues/issue1825/overlay_test.go b/internal/test/issues/issue1825/overlay_test.go new file mode 100644 index 0000000000..115a878fad --- /dev/null +++ b/internal/test/issues/issue1825/overlay_test.go @@ -0,0 +1,14 @@ +package issue1825 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOverlayApply(t *testing.T) { + spec, err := GetSwagger() + require.NoError(t, err) + + require.Equal(t, spec.Info.Extensions["x-overlay-applied"], "structured-overlay") +} diff --git a/internal/test/issues/issue1825/packageA/config.yaml b/internal/test/issues/issue1825/packageA/config.yaml new file mode 100644 index 0000000000..e84dbfad74 --- /dev/null +++ b/internal/test/issues/issue1825/packageA/config.yaml @@ -0,0 +1,7 @@ +package: packagea +generate: + models: true + embedded-spec: true +output-options: + skip-prune: true +output: externalref.gen.go diff --git a/internal/test/issues/issue1825/packageA/doc.go b/internal/test/issues/issue1825/packageA/doc.go new file mode 100644 index 0000000000..f05471ffbb --- /dev/null +++ b/internal/test/issues/issue1825/packageA/doc.go @@ -0,0 +1,3 @@ +package packagea + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/issues/issue1825/packageA/externalref.gen.go b/internal/test/issues/issue1825/packageA/externalref.gen.go new file mode 100644 index 0000000000..b2d5ca870b --- /dev/null +++ b/internal/test/issues/issue1825/packageA/externalref.gen.go @@ -0,0 +1,100 @@ +// Package packagea provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package packagea + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" +) + +// ObjectA defines model for ObjectA. +type ObjectA struct { + Name *string `json:"name,omitempty"` +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/yTJwQ0CMQxE0V7mnApyowJqCNHAGm1sKzYHtNreUZa5zJfegW7DTakZqAeibxztyvvj", + "zZ63lT7NOVN4gbbB9fl1oiJyir5wrhWIPg1VP/teYE5tLqhAgbfc4i/nLwAA//+kHCeTdgAAAA==", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/issues/issue1825/packageA/spec.yaml b/internal/test/issues/issue1825/packageA/spec.yaml new file mode 100644 index 0000000000..303c509938 --- /dev/null +++ b/internal/test/issues/issue1825/packageA/spec.yaml @@ -0,0 +1,6 @@ +components: + schemas: + ObjectA: + properties: + name: + type: string diff --git a/internal/test/issues/issue1825/spec/spec.yaml b/internal/test/issues/issue1825/spec/spec.yaml new file mode 100644 index 0000000000..3a29a23582 --- /dev/null +++ b/internal/test/issues/issue1825/spec/spec.yaml @@ -0,0 +1,11 @@ +openapi: "3.0.0" +info: {} +paths: {} +components: + schemas: + Container: + properties: + object_a: + $ref: ../packageA/spec.yaml#/components/schemas/ObjectA + object_b: + $ref: ../object_b.json diff --git a/internal/test/issues/issue193/config.yaml b/internal/test/issues/issue193/config.yaml new file mode 100644 index 0000000000..908d772c69 --- /dev/null +++ b/internal/test/issues/issue193/config.yaml @@ -0,0 +1,6 @@ +package: issue52 +generate: + models: true +output: issue.gen.go +output-options: + skip-prune: true diff --git a/internal/test/issues/issue193/generate.go b/internal/test/issues/issue193/generate.go new file mode 100644 index 0000000000..3d239ebdcf --- /dev/null +++ b/internal/test/issues/issue193/generate.go @@ -0,0 +1,3 @@ +package issue52 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/issues/issue193/issue.gen.go b/internal/test/issues/issue193/issue.gen.go new file mode 100644 index 0000000000..eec8a25dfc --- /dev/null +++ b/internal/test/issues/issue193/issue.gen.go @@ -0,0 +1,113 @@ +// Package issue52 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue52 + +import ( + "encoding/json" + "fmt" +) + +// Person defines model for Person. +type Person struct { + Age *float32 `json:"age,omitempty"` + Metadata string `json:"metadata"` + Name *string `json:"name,omitempty"` + AdditionalProperties map[string]interface{} `json:"-"` +} + +// Getter for additional properties for Person. Returns the specified +// element and whether it was found +func (a Person) Get(fieldName string) (value interface{}, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for Person +func (a *Person) Set(fieldName string, value interface{}) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]interface{}) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for Person to handle AdditionalProperties +func (a *Person) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["age"]; found { + err = json.Unmarshal(raw, &a.Age) + if err != nil { + return fmt.Errorf("error reading 'age': %w", err) + } + delete(object, "age") + } + + if raw, found := object["metadata"]; found { + err = json.Unmarshal(raw, &a.Metadata) + if err != nil { + return fmt.Errorf("error reading 'metadata': %w", err) + } + delete(object, "metadata") + } + + if raw, found := object["name"]; found { + err = json.Unmarshal(raw, &a.Name) + if err != nil { + return fmt.Errorf("error reading 'name': %w", err) + } + delete(object, "name") + } + + if len(object) != 0 { + a.AdditionalProperties = make(map[string]interface{}) + for fieldName, fieldBuf := range object { + var fieldVal interface{} + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for Person to handle AdditionalProperties +func (a Person) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + if a.Age != nil { + object["age"], err = json.Marshal(a.Age) + if err != nil { + return nil, fmt.Errorf("error marshaling 'age': %w", err) + } + } + + object["metadata"], err = json.Marshal(a.Metadata) + if err != nil { + return nil, fmt.Errorf("error marshaling 'metadata': %w", err) + } + + if a.Name != nil { + object["name"], err = json.Marshal(a.Name) + if err != nil { + return nil, fmt.Errorf("error marshaling 'name': %w", err) + } + } + + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) +} diff --git a/internal/test/issues/issue193/spec.yaml b/internal/test/issues/issue193/spec.yaml new file mode 100644 index 0000000000..4002ffbca5 --- /dev/null +++ b/internal/test/issues/issue193/spec.yaml @@ -0,0 +1,26 @@ +openapi: 3.0.0 +info: + title: test schema + +paths: + +components: + schemas: + Person: + allOf: + # common fields + - type: object + additionalProperties: true + required: + - metadata + properties: + metadata: + type: string + # person specific fields + - type: object + additionalProperties: true + properties: + name: + type: string + age: + type: number diff --git a/internal/test/issues/issue1957/config.yaml b/internal/test/issues/issue1957/config.yaml new file mode 100644 index 0000000000..42b31a19f6 --- /dev/null +++ b/internal/test/issues/issue1957/config.yaml @@ -0,0 +1,6 @@ +package: issue1957 +generate: + models: true +output: issue1957.gen.go +output-options: + skip-prune: true diff --git a/internal/test/issues/issue1957/generate.go b/internal/test/issues/issue1957/generate.go new file mode 100644 index 0000000000..1a7c0002be --- /dev/null +++ b/internal/test/issues/issue1957/generate.go @@ -0,0 +1,3 @@ +package issue1957 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml openapi.yaml diff --git a/internal/test/issues/issue1957/issue1957.gen.go b/internal/test/issues/issue1957/issue1957.gen.go new file mode 100644 index 0000000000..ee2c7adb75 --- /dev/null +++ b/internal/test/issues/issue1957/issue1957.gen.go @@ -0,0 +1,27 @@ +// Package issue1957 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue1957 + +import ( + googleuuid "github.com/google/uuid" +) + +// ID defines model for ID. +type ID = googleuuid.UUID + +// TypeWithAllOf defines model for TypeWithAllOf. +type TypeWithAllOf struct { + Id googleuuid.UUID `json:"id,omitempty"` +} + +// TypeWithOptionalField defines model for TypeWithOptionalField. +type TypeWithOptionalField struct { + At googleuuid.UUID `json:"at,omitempty"` + AtRequired googleuuid.UUID `json:"at_required"` +} + +// GetRootParams defines parameters for GetRoot. +type GetRootParams struct { + At googleuuid.UUID `form:"at,omitempty" json:"at,omitempty"` +} diff --git a/internal/test/issues/issue1957/issue1957_test.go b/internal/test/issues/issue1957/issue1957_test.go new file mode 100644 index 0000000000..de99ccca36 --- /dev/null +++ b/internal/test/issues/issue1957/issue1957_test.go @@ -0,0 +1,54 @@ +package issue1957 + +import ( + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" +) + +func TestGeneratedCode(t *testing.T) { + t.Run("For an object", func(t *testing.T) { + t.Run("A required field should be a non-pointer", func(t *testing.T) { + theType := TypeWithOptionalField{ + AtRequired: uuid.New(), + } + + require.NotZero(t, theType.AtRequired) + }) + + t.Run("An optional field with x-go-type-skip-optional-pointer should be a non-pointer", func(t *testing.T) { + theType := TypeWithOptionalField{ + AtRequired: uuid.New(), + } + + require.NotZero(t, theType.AtRequired) + }) + }) + + t.Run("For a query parameter", func(t *testing.T) { + t.Run("An optional field with x-go-type-skip-optional-pointer should be a non-pointer", func(t *testing.T) { + + u := uuid.New() + + theType := GetRootParams{ + At: u, + } + + require.NotZero(t, theType.At) + }) + }) + + t.Run("For a field with an AllOf", func(t *testing.T) { + t.Run("An optional field with x-go-type-skip-optional-pointer should be a non-pointer", func(t *testing.T) { + + u := uuid.New() + + theType := TypeWithAllOf{ + Id: u, + } + + require.NotZero(t, theType.Id) + }) + }) +} diff --git a/internal/test/issues/issue1957/openapi.yaml b/internal/test/issues/issue1957/openapi.yaml new file mode 100644 index 0000000000..729c71dac9 --- /dev/null +++ b/internal/test/issues/issue1957/openapi.yaml @@ -0,0 +1,55 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: "x-go-type and x-go-type-skip-optional-pointer should be possible to use together" +paths: + /root: + get: + operationId: getRoot + parameters: + - in: query + name: at + schema: + type: string + format: date-time + x-go-type-skip-optional-pointer: true + x-go-type: googleuuid.UUID + x-go-type-import: + path: github.com/google/uuid + name: googleuuid + responses: + "200": + description: Some data +components: + schemas: + TypeWithOptionalField: + type: object + properties: + at: + type: string + x-go-type-skip-optional-pointer: true + x-go-type: googleuuid.UUID + x-go-type-import: + path: github.com/google/uuid + name: googleuuid + at_required: + type: string + x-go-type: googleuuid.UUID + x-go-type-import: + path: github.com/google/uuid + name: googleuuid + required: + - at_required + ID: + type: string + x-go-type: googleuuid.UUID + x-go-type-import: + path: github.com/google/uuid + name: googleuuid + TypeWithAllOf: + type: object + properties: + id: + allOf: + - $ref: '#/components/schemas/ID' + - x-go-type-skip-optional-pointer: true diff --git a/internal/test/issues/issue240/api.yaml b/internal/test/issues/issue240/api.yaml new file mode 100644 index 0000000000..5945b63dd7 --- /dev/null +++ b/internal/test/issues/issue240/api.yaml @@ -0,0 +1,47 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Generate models +paths: + /client: + get: + operationId: getClient + responses: + 200: + content: + application/json: + schema: + $ref: "#/components/schemas/ClientType" + put: + operationId: updateClient + responses: + 400: + content: + application/json: + schema: + type: object + properties: + code: + type: string + required: + - code +components: + schemas: + ClientType: + type: object + required: + - name + properties: + name: + type: string + # NOTE that this is not generated by default because it's not referenced. If you want it, you need to use the following YAML configuration: + # + # output-options: + # skip-prune: true + Unreferenced: + type: object + required: + - id + properties: + id: + type: int diff --git a/internal/test/issues/issue240/cfg.yaml b/internal/test/issues/issue240/cfg.yaml new file mode 100644 index 0000000000..e3143764a0 --- /dev/null +++ b/internal/test/issues/issue240/cfg.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: issue240 +output: client.gen.go +generate: + models: true + client: true +output-options: + client-response-bytes-function: true diff --git a/internal/test/issues/issue240/client.gen.go b/internal/test/issues/issue240/client.gen.go new file mode 100644 index 0000000000..8a0a533589 --- /dev/null +++ b/internal/test/issues/issue240/client.gen.go @@ -0,0 +1,355 @@ +// Package issue240 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue240 + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" +) + +// ClientType defines model for ClientType. +type ClientType struct { + Name string `json:"name"` +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetClient request + GetClient(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UpdateClient request + UpdateClient(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) GetClient(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetClientRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateClient(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateClientRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetClientRequest generates requests for GetClient +func NewGetClientRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/client") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewUpdateClientRequest generates requests for UpdateClient +func NewUpdateClientRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/client") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PUT", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetClientWithResponse request + GetClientWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetClientResponse, error) + + // UpdateClientWithResponse request + UpdateClientWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UpdateClientResponse, error) +} + +type GetClientResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *ClientType +} + +// Status returns HTTPResponse.Status +func (r GetClientResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetClientResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// Bytes is a convenience method to retrieve the raw bytes from the HTTP response +func (r GetClientResponse) Bytes() []byte { + return r.Body +} + +type UpdateClientResponse struct { + Body []byte + HTTPResponse *http.Response + JSON400 *struct { + Code string `json:"code"` + } +} + +// Status returns HTTPResponse.Status +func (r UpdateClientResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateClientResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// Bytes is a convenience method to retrieve the raw bytes from the HTTP response +func (r UpdateClientResponse) Bytes() []byte { + return r.Body +} + +// GetClientWithResponse request returning *GetClientResponse +func (c *ClientWithResponses) GetClientWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetClientResponse, error) { + rsp, err := c.GetClient(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetClientResponse(rsp) +} + +// UpdateClientWithResponse request returning *UpdateClientResponse +func (c *ClientWithResponses) UpdateClientWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UpdateClientResponse, error) { + rsp, err := c.UpdateClient(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateClientResponse(rsp) +} + +// ParseGetClientResponse parses an HTTP response from a GetClientWithResponse call +func ParseGetClientResponse(rsp *http.Response) (*GetClientResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetClientResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest ClientType + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseUpdateClientResponse parses an HTTP response from a UpdateClientWithResponse call +func ParseUpdateClientResponse(rsp *http.Response) (*UpdateClientResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateClientResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest struct { + Code string `json:"code"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + } + + return response, nil +} diff --git a/internal/test/issues/issue240/generate.go b/internal/test/issues/issue240/generate.go new file mode 100644 index 0000000000..a107ac4597 --- /dev/null +++ b/internal/test/issues/issue240/generate.go @@ -0,0 +1,3 @@ +package issue240 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/internal/test/issues/issue609/config.yaml b/internal/test/issues/issue609/config.yaml new file mode 100644 index 0000000000..a9c4b93879 --- /dev/null +++ b/internal/test/issues/issue609/config.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: issue609 +generate: + models: true +output: issue609.gen.go +output-options: + skip-prune: true diff --git a/internal/test/issues/issue609/generate.go b/internal/test/issues/issue609/generate.go new file mode 100644 index 0000000000..ade5eb92fb --- /dev/null +++ b/internal/test/issues/issue609/generate.go @@ -0,0 +1,3 @@ +package issue609 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml openapi.yaml diff --git a/internal/test/issues/issue609/issue609.gen.go b/internal/test/issues/issue609/issue609.gen.go new file mode 100644 index 0000000000..a5d76e500d --- /dev/null +++ b/internal/test/issues/issue609/issue609.gen.go @@ -0,0 +1,9 @@ +// Package issue609 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package issue609 + +// ResponseBody defines model for ResponseBody. +type ResponseBody struct { + Unknown interface{} `json:"unknown,omitempty"` +} diff --git a/internal/test/issues/issue609/openapi.yaml b/internal/test/issues/issue609/openapi.yaml new file mode 100644 index 0000000000..d7dae7fdf3 --- /dev/null +++ b/internal/test/issues/issue609/openapi.yaml @@ -0,0 +1,11 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: "Referencing an optional field, which has no information about the type it is will generate an `interface{}`, without the 'optional pointer'" +paths: +components: + schemas: + ResponseBody: + type: object + properties: + unknown: {} diff --git a/internal/test/name_conflict_resolution/config.yaml b/internal/test/name_conflict_resolution/config.yaml new file mode 100644 index 0000000000..6e173b0a4e --- /dev/null +++ b/internal/test/name_conflict_resolution/config.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../configuration-schema.json +package: nameconflictresolution +generate: + models: true + client: true +output: name_conflict_resolution.gen.go +output-options: + resolve-type-name-collisions: true diff --git a/internal/test/name_conflict_resolution/doc.go b/internal/test/name_conflict_resolution/doc.go new file mode 100644 index 0000000000..4d577571ef --- /dev/null +++ b/internal/test/name_conflict_resolution/doc.go @@ -0,0 +1,3 @@ +package nameconflictresolution + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/name_conflict_resolution/name_conflict_resolution.gen.go b/internal/test/name_conflict_resolution/name_conflict_resolution.gen.go new file mode 100644 index 0000000000..068f8e6d4a --- /dev/null +++ b/internal/test/name_conflict_resolution/name_conflict_resolution.gen.go @@ -0,0 +1,3000 @@ +// Package nameconflictresolution provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package nameconflictresolution + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/oapi-codegen/runtime" +) + +// Bar defines model for Bar. +type Bar struct { + Value *string `json:"value,omitempty"` +} + +// Bar2 defines model for Bar2. +type Bar2 struct { + Value *float32 `json:"value,omitempty"` +} + +// CreateItemResponse defines model for CreateItemResponse. +type CreateItemResponse struct { + Id *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` +} + +// GetStatusResponse defines model for GetStatusResponse. +type GetStatusResponse struct { + Status *string `json:"status,omitempty"` + Timestamp *string `json:"timestamp,omitempty"` +} + +// JsonPatch defines model for JsonPatch. +type JsonPatch = []struct { + Op *string `json:"op,omitempty"` + Path *string `json:"path,omitempty"` +} + +// ListItemsResponse defines model for ListItemsResponse. +type ListItemsResponse = string + +// Metadata defines model for Metadata. +type Metadata = string + +// Order defines model for Order. +type Order struct { + Id *string `json:"id,omitempty"` + Product *string `json:"product,omitempty"` +} + +// Outcome defines model for Outcome. +type Outcome struct { + Value *string `json:"value,omitempty"` +} + +// Payload defines model for Payload. +type Payload struct { + Content *string `json:"content,omitempty"` +} + +// Pet defines model for Pet. +type Pet struct { + Id *int `json:"id,omitempty"` + Name *string `json:"name,omitempty"` +} + +// QueryResponse defines model for QueryResponse. +type QueryResponse struct { + Results *[]string `json:"results,omitempty"` +} + +// Qux defines model for Qux. +type Qux = CustomQux + +// CustomQux defines model for . +type CustomQux struct { + Label *string `json:"label,omitempty"` +} + +// SpecialName defines model for Renamer. +type SpecialName struct { + Label *string `json:"label,omitempty"` +} + +// Resource defines model for Resource. +type Resource struct { + Id *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Status *string `json:"status,omitempty"` +} + +// ResourceMVO defines model for Resource_MVO. +type ResourceMVO struct { + Name *string `json:"name,omitempty"` + Status *string `json:"status,omitempty"` +} + +// Widget defines model for Widget. +type Widget = string + +// Zap defines model for Zap. +type Zap = string + +// BarParameter defines model for Bar. +type BarParameter = string + +// N200ResourcePatchResponseJSONApplicationJSON defines model for 200Resource_Patch. +type N200ResourcePatchResponseJSONApplicationJSON = Resource + +// N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON defines model for 200Resource_Patch. +type N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON struct { + union json.RawMessage +} + +// N200ResourcePatchApplicationJSONPatchPlusJSON1 defines model for . +type N200ResourcePatchApplicationJSONPatchPlusJSON1 = []Resource + +// N200ResourcePatchApplicationJSONPatchPlusJSON2 defines model for . +type N200ResourcePatchApplicationJSONPatchPlusJSON2 = string + +// N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON defines model for 200Resource_Patch. +type N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON struct { + union json.RawMessage +} + +// N200ResourcePatchApplicationJSONPatchQueryPlusJSON1 defines model for . +type N200ResourcePatchApplicationJSONPatchQueryPlusJSON1 = []Resource + +// N200ResourcePatchApplicationJSONPatchQueryPlusJSON2 defines model for . +type N200ResourcePatchApplicationJSONPatchQueryPlusJSON2 = string + +// N200ResourcePatchResponseJSON4ApplicationMergePatchPlusJSON defines model for 200Resource_Patch. +type N200ResourcePatchResponseJSON4ApplicationMergePatchPlusJSON = Resource + +// BarResponse defines model for Bar. +type BarResponse struct { + Value1 *Bar `json:"value1,omitempty"` + Value2 *Bar2 `json:"value2,omitempty"` +} + +// OutcomeResult defines model for Outcome. +type OutcomeResult struct { + Result *string `json:"result,omitempty"` +} + +// QuxResponse defines model for Qux. +type QuxResponse struct { + Data *string `json:"data,omitempty"` +} + +// Renamer defines model for Renamer. +type Renamer struct { + Data *string `json:"data,omitempty"` +} + +// ZapResponse defines model for Zap. +type ZapResponse struct { + Result *string `json:"result,omitempty"` +} + +// BarRequestBody defines model for Bar. +type BarRequestBody struct { + Value *int `json:"value,omitempty"` +} + +// OrderRequestBodyJSON defines model for Order. +type OrderRequestBodyJSON struct { + Id *string `json:"id,omitempty"` + Product *string `json:"product,omitempty"` +} + +// OrderRequestBodyJSON2 defines model for Order. +type OrderRequestBodyJSON2 = []struct { + Op *string `json:"op,omitempty"` + Path *string `json:"path,omitempty"` + Value *string `json:"value,omitempty"` +} + +// OrderRequestBodyJSON3 defines model for Order. +type OrderRequestBodyJSON3 struct { + Product *string `json:"product,omitempty"` +} + +// PayloadBody defines model for Payload. +type PayloadBody struct { + Data *string `json:"data,omitempty"` +} + +// PetRequestBody defines model for Pet. +type PetRequestBody struct { + Name *string `json:"name,omitempty"` + Species *string `json:"species,omitempty"` +} + +// ResourceMVORequestBodyJSON defines model for Resource_MVO. +type ResourceMVORequestBodyJSON = ResourceMVO + +// ResourceMVORequestBodyJSON2 defines model for Resource_MVO. +type ResourceMVORequestBodyJSON2 = JsonPatch + +// ResourceMVORequestBodyJSON3 defines model for Resource_MVO. +type ResourceMVORequestBodyJSON3 = ResourceMVO + +// PostFooJSONBody defines parameters for PostFoo. +type PostFooJSONBody struct { + Value *int `json:"value,omitempty"` +} + +// PostFooParams defines parameters for PostFoo. +type PostFooParams struct { + Bar *BarParameter `form:"bar,omitempty" json:"bar,omitempty"` +} + +// CreateItemJSONBody defines parameters for CreateItem. +type CreateItemJSONBody struct { + Name *string `json:"name,omitempty"` +} + +// CreateOrderJSONBody defines parameters for CreateOrder. +type CreateOrderJSONBody struct { + Id *string `json:"id,omitempty"` + Product *string `json:"product,omitempty"` +} + +// CreateOrderApplicationJSONPatchPlusJSONBody defines parameters for CreateOrder. +type CreateOrderApplicationJSONPatchPlusJSONBody = []struct { + Op *string `json:"op,omitempty"` + Path *string `json:"path,omitempty"` + Value *string `json:"value,omitempty"` +} + +// CreateOrderApplicationMergePatchPlusJSONBody defines parameters for CreateOrder. +type CreateOrderApplicationMergePatchPlusJSONBody struct { + Product *string `json:"product,omitempty"` +} + +// SendPayloadJSONBody defines parameters for SendPayload. +type SendPayloadJSONBody struct { + Data *string `json:"data,omitempty"` +} + +// CreatePetJSONBody defines parameters for CreatePet. +type CreatePetJSONBody struct { + Name *string `json:"name,omitempty"` + Species *string `json:"species,omitempty"` +} + +// QueryJSONBody defines parameters for Query. +type QueryJSONBody struct { + Q *string `json:"q,omitempty"` +} + +// PostFooJSONRequestBody defines body for PostFoo for application/json ContentType. +type PostFooJSONRequestBody PostFooJSONBody + +// CreateItemJSONRequestBody defines body for CreateItem for application/json ContentType. +type CreateItemJSONRequestBody CreateItemJSONBody + +// CreateOrderJSONRequestBody defines body for CreateOrder for application/json ContentType. +type CreateOrderJSONRequestBody CreateOrderJSONBody + +// CreateOrderApplicationJSONPatchPlusJSONRequestBody defines body for CreateOrder for application/json-patch+json ContentType. +type CreateOrderApplicationJSONPatchPlusJSONRequestBody = CreateOrderApplicationJSONPatchPlusJSONBody + +// CreateOrderApplicationMergePatchPlusJSONRequestBody defines body for CreateOrder for application/merge-patch+json ContentType. +type CreateOrderApplicationMergePatchPlusJSONRequestBody CreateOrderApplicationMergePatchPlusJSONBody + +// PostOutcomeJSONRequestBody defines body for PostOutcome for application/json ContentType. +type PostOutcomeJSONRequestBody = Outcome + +// SendPayloadJSONRequestBody defines body for SendPayload for application/json ContentType. +type SendPayloadJSONRequestBody SendPayloadJSONBody + +// CreatePetJSONRequestBody defines body for CreatePet for application/json ContentType. +type CreatePetJSONRequestBody CreatePetJSONBody + +// QueryJSONRequestBody defines body for Query for application/json ContentType. +type QueryJSONRequestBody QueryJSONBody + +// PostQuxJSONRequestBody defines body for PostQux for application/json ContentType. +type PostQuxJSONRequestBody = Qux + +// PostRenamedSchemaJSONRequestBody defines body for PostRenamedSchema for application/json ContentType. +type PostRenamedSchemaJSONRequestBody = SpecialName + +// PatchResourceJSONRequestBody defines body for PatchResource for application/json ContentType. +type PatchResourceJSONRequestBody = ResourceMVO + +// PatchResourceApplicationJSONPatchPlusJSONRequestBody defines body for PatchResource for application/json-patch+json ContentType. +type PatchResourceApplicationJSONPatchPlusJSONRequestBody = JsonPatch + +// PatchResourceApplicationMergePatchPlusJSONRequestBody defines body for PatchResource for application/merge-patch+json ContentType. +type PatchResourceApplicationMergePatchPlusJSONRequestBody = ResourceMVO + +// PostZapJSONRequestBody defines body for PostZap for application/json ContentType. +type PostZapJSONRequestBody = Zap + +// AsResource returns the union data inside the N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON as a Resource +func (t N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON) AsResource() (Resource, error) { + var body Resource + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromResource overwrites any union data inside the N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON as the provided Resource +func (t *N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON) FromResource(v Resource) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeResource performs a merge with any union data inside the N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON, using the provided Resource +func (t *N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON) MergeResource(v Resource) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsN200ResourcePatchApplicationJSONPatchPlusJSON1 returns the union data inside the N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON as a N200ResourcePatchApplicationJSONPatchPlusJSON1 +func (t N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON) AsN200ResourcePatchApplicationJSONPatchPlusJSON1() (N200ResourcePatchApplicationJSONPatchPlusJSON1, error) { + var body N200ResourcePatchApplicationJSONPatchPlusJSON1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromN200ResourcePatchApplicationJSONPatchPlusJSON1 overwrites any union data inside the N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON as the provided N200ResourcePatchApplicationJSONPatchPlusJSON1 +func (t *N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON) FromN200ResourcePatchApplicationJSONPatchPlusJSON1(v N200ResourcePatchApplicationJSONPatchPlusJSON1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeN200ResourcePatchApplicationJSONPatchPlusJSON1 performs a merge with any union data inside the N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON, using the provided N200ResourcePatchApplicationJSONPatchPlusJSON1 +func (t *N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON) MergeN200ResourcePatchApplicationJSONPatchPlusJSON1(v N200ResourcePatchApplicationJSONPatchPlusJSON1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsN200ResourcePatchApplicationJSONPatchPlusJSON2 returns the union data inside the N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON as a N200ResourcePatchApplicationJSONPatchPlusJSON2 +func (t N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON) AsN200ResourcePatchApplicationJSONPatchPlusJSON2() (N200ResourcePatchApplicationJSONPatchPlusJSON2, error) { + var body N200ResourcePatchApplicationJSONPatchPlusJSON2 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromN200ResourcePatchApplicationJSONPatchPlusJSON2 overwrites any union data inside the N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON as the provided N200ResourcePatchApplicationJSONPatchPlusJSON2 +func (t *N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON) FromN200ResourcePatchApplicationJSONPatchPlusJSON2(v N200ResourcePatchApplicationJSONPatchPlusJSON2) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeN200ResourcePatchApplicationJSONPatchPlusJSON2 performs a merge with any union data inside the N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON, using the provided N200ResourcePatchApplicationJSONPatchPlusJSON2 +func (t *N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON) MergeN200ResourcePatchApplicationJSONPatchPlusJSON2(v N200ResourcePatchApplicationJSONPatchPlusJSON2) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsResource returns the union data inside the N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON as a Resource +func (t N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON) AsResource() (Resource, error) { + var body Resource + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromResource overwrites any union data inside the N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON as the provided Resource +func (t *N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON) FromResource(v Resource) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeResource performs a merge with any union data inside the N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON, using the provided Resource +func (t *N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON) MergeResource(v Resource) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsN200ResourcePatchApplicationJSONPatchQueryPlusJSON1 returns the union data inside the N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON as a N200ResourcePatchApplicationJSONPatchQueryPlusJSON1 +func (t N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON) AsN200ResourcePatchApplicationJSONPatchQueryPlusJSON1() (N200ResourcePatchApplicationJSONPatchQueryPlusJSON1, error) { + var body N200ResourcePatchApplicationJSONPatchQueryPlusJSON1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromN200ResourcePatchApplicationJSONPatchQueryPlusJSON1 overwrites any union data inside the N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON as the provided N200ResourcePatchApplicationJSONPatchQueryPlusJSON1 +func (t *N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON) FromN200ResourcePatchApplicationJSONPatchQueryPlusJSON1(v N200ResourcePatchApplicationJSONPatchQueryPlusJSON1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeN200ResourcePatchApplicationJSONPatchQueryPlusJSON1 performs a merge with any union data inside the N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON, using the provided N200ResourcePatchApplicationJSONPatchQueryPlusJSON1 +func (t *N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON) MergeN200ResourcePatchApplicationJSONPatchQueryPlusJSON1(v N200ResourcePatchApplicationJSONPatchQueryPlusJSON1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsN200ResourcePatchApplicationJSONPatchQueryPlusJSON2 returns the union data inside the N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON as a N200ResourcePatchApplicationJSONPatchQueryPlusJSON2 +func (t N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON) AsN200ResourcePatchApplicationJSONPatchQueryPlusJSON2() (N200ResourcePatchApplicationJSONPatchQueryPlusJSON2, error) { + var body N200ResourcePatchApplicationJSONPatchQueryPlusJSON2 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromN200ResourcePatchApplicationJSONPatchQueryPlusJSON2 overwrites any union data inside the N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON as the provided N200ResourcePatchApplicationJSONPatchQueryPlusJSON2 +func (t *N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON) FromN200ResourcePatchApplicationJSONPatchQueryPlusJSON2(v N200ResourcePatchApplicationJSONPatchQueryPlusJSON2) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeN200ResourcePatchApplicationJSONPatchQueryPlusJSON2 performs a merge with any union data inside the N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON, using the provided N200ResourcePatchApplicationJSONPatchQueryPlusJSON2 +func (t *N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON) MergeN200ResourcePatchApplicationJSONPatchQueryPlusJSON2(v N200ResourcePatchApplicationJSONPatchQueryPlusJSON2) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // ListEntities request + ListEntities(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // PostFooWithBody request with any body + PostFooWithBody(ctx context.Context, params *PostFooParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + PostFoo(ctx context.Context, params *PostFooParams, body PostFooJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ListItems request + ListItems(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateItemWithBody request with any body + CreateItemWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateItem(ctx context.Context, body CreateItemJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateOrderWithBody request with any body + CreateOrderWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateOrder(ctx context.Context, body CreateOrderJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateOrderWithApplicationJSONPatchPlusJSONBody(ctx context.Context, body CreateOrderApplicationJSONPatchPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateOrderWithApplicationMergePatchPlusJSONBody(ctx context.Context, body CreateOrderApplicationMergePatchPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetOutcome request + GetOutcome(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // PostOutcomeWithBody request with any body + PostOutcomeWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + PostOutcome(ctx context.Context, body PostOutcomeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // SendPayloadWithBody request with any body + SendPayloadWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + SendPayload(ctx context.Context, body SendPayloadJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreatePetWithBody request with any body + CreatePetWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreatePet(ctx context.Context, body CreatePetJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // QueryWithBody request with any body + QueryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + Query(ctx context.Context, body QueryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetQux request + GetQux(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // PostQuxWithBody request with any body + PostQuxWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + PostQux(ctx context.Context, body PostQuxJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetRenamedSchema request + GetRenamedSchema(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // PostRenamedSchemaWithBody request with any body + PostRenamedSchemaWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + PostRenamedSchema(ctx context.Context, body PostRenamedSchemaJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // PatchResourceWithBody request with any body + PatchResourceWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + PatchResource(ctx context.Context, id string, body PatchResourceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + PatchResourceWithApplicationJSONPatchPlusJSONBody(ctx context.Context, id string, body PatchResourceApplicationJSONPatchPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + PatchResourceWithApplicationMergePatchPlusJSONBody(ctx context.Context, id string, body PatchResourceApplicationMergePatchPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetStatus request + GetStatus(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetZap request + GetZap(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // PostZapWithBody request with any body + PostZapWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + PostZap(ctx context.Context, body PostZapJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) ListEntities(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListEntitiesRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PostFooWithBody(ctx context.Context, params *PostFooParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostFooRequestWithBody(c.Server, params, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PostFoo(ctx context.Context, params *PostFooParams, body PostFooJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostFooRequest(c.Server, params, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ListItems(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListItemsRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateItemWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateItemRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateItem(ctx context.Context, body CreateItemJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateItemRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateOrderWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateOrderRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateOrder(ctx context.Context, body CreateOrderJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateOrderRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateOrderWithApplicationJSONPatchPlusJSONBody(ctx context.Context, body CreateOrderApplicationJSONPatchPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateOrderRequestWithApplicationJSONPatchPlusJSONBody(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateOrderWithApplicationMergePatchPlusJSONBody(ctx context.Context, body CreateOrderApplicationMergePatchPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateOrderRequestWithApplicationMergePatchPlusJSONBody(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetOutcome(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetOutcomeRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PostOutcomeWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostOutcomeRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PostOutcome(ctx context.Context, body PostOutcomeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostOutcomeRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) SendPayloadWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSendPayloadRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) SendPayload(ctx context.Context, body SendPayloadJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSendPayloadRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreatePetWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreatePetRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreatePet(ctx context.Context, body CreatePetJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreatePetRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) QueryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewQueryRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) Query(ctx context.Context, body QueryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewQueryRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetQux(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetQuxRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PostQuxWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostQuxRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PostQux(ctx context.Context, body PostQuxJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostQuxRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetRenamedSchema(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetRenamedSchemaRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PostRenamedSchemaWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostRenamedSchemaRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PostRenamedSchema(ctx context.Context, body PostRenamedSchemaJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostRenamedSchemaRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PatchResourceWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchResourceRequestWithBody(c.Server, id, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PatchResource(ctx context.Context, id string, body PatchResourceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchResourceRequest(c.Server, id, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PatchResourceWithApplicationJSONPatchPlusJSONBody(ctx context.Context, id string, body PatchResourceApplicationJSONPatchPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchResourceRequestWithApplicationJSONPatchPlusJSONBody(c.Server, id, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PatchResourceWithApplicationMergePatchPlusJSONBody(ctx context.Context, id string, body PatchResourceApplicationMergePatchPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchResourceRequestWithApplicationMergePatchPlusJSONBody(c.Server, id, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetStatus(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetStatusRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetZap(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetZapRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PostZapWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostZapRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PostZap(ctx context.Context, body PostZapJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostZapRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewListEntitiesRequest generates requests for ListEntities +func NewListEntitiesRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/entities") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewPostFooRequest calls the generic PostFoo builder with application/json body +func NewPostFooRequest(server string, params *PostFooParams, body PostFooJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPostFooRequestWithBody(server, params, "application/json", bodyReader) +} + +// NewPostFooRequestWithBody generates requests for PostFoo with any type of body +func NewPostFooRequestWithBody(server string, params *PostFooParams, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/foo") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Bar != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "bar", *params.Bar, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewListItemsRequest generates requests for ListItems +func NewListItemsRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/items") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateItemRequest calls the generic CreateItem builder with application/json body +func NewCreateItemRequest(server string, body CreateItemJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateItemRequestWithBody(server, "application/json", bodyReader) +} + +// NewCreateItemRequestWithBody generates requests for CreateItem with any type of body +func NewCreateItemRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/items") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewCreateOrderRequest calls the generic CreateOrder builder with application/json body +func NewCreateOrderRequest(server string, body CreateOrderJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateOrderRequestWithBody(server, "application/json", bodyReader) +} + +// NewCreateOrderRequestWithApplicationJSONPatchPlusJSONBody calls the generic CreateOrder builder with application/json-patch+json body +func NewCreateOrderRequestWithApplicationJSONPatchPlusJSONBody(server string, body CreateOrderApplicationJSONPatchPlusJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateOrderRequestWithBody(server, "application/json-patch+json", bodyReader) +} + +// NewCreateOrderRequestWithApplicationMergePatchPlusJSONBody calls the generic CreateOrder builder with application/merge-patch+json body +func NewCreateOrderRequestWithApplicationMergePatchPlusJSONBody(server string, body CreateOrderApplicationMergePatchPlusJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateOrderRequestWithBody(server, "application/merge-patch+json", bodyReader) +} + +// NewCreateOrderRequestWithBody generates requests for CreateOrder with any type of body +func NewCreateOrderRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/orders") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewGetOutcomeRequest generates requests for GetOutcome +func NewGetOutcomeRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/outcome") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewPostOutcomeRequest calls the generic PostOutcome builder with application/json body +func NewPostOutcomeRequest(server string, body PostOutcomeJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPostOutcomeRequestWithBody(server, "application/json", bodyReader) +} + +// NewPostOutcomeRequestWithBody generates requests for PostOutcome with any type of body +func NewPostOutcomeRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/outcome") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewSendPayloadRequest calls the generic SendPayload builder with application/json body +func NewSendPayloadRequest(server string, body SendPayloadJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewSendPayloadRequestWithBody(server, "application/json", bodyReader) +} + +// NewSendPayloadRequestWithBody generates requests for SendPayload with any type of body +func NewSendPayloadRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/payload") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewCreatePetRequest calls the generic CreatePet builder with application/json body +func NewCreatePetRequest(server string, body CreatePetJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreatePetRequestWithBody(server, "application/json", bodyReader) +} + +// NewCreatePetRequestWithBody generates requests for CreatePet with any type of body +func NewCreatePetRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/pets") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewQueryRequest calls the generic Query builder with application/json body +func NewQueryRequest(server string, body QueryJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewQueryRequestWithBody(server, "application/json", bodyReader) +} + +// NewQueryRequestWithBody generates requests for Query with any type of body +func NewQueryRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/query") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewGetQuxRequest generates requests for GetQux +func NewGetQuxRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/qux") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewPostQuxRequest calls the generic PostQux builder with application/json body +func NewPostQuxRequest(server string, body PostQuxJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPostQuxRequestWithBody(server, "application/json", bodyReader) +} + +// NewPostQuxRequestWithBody generates requests for PostQux with any type of body +func NewPostQuxRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/qux") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewGetRenamedSchemaRequest generates requests for GetRenamedSchema +func NewGetRenamedSchemaRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/renamed-schema") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewPostRenamedSchemaRequest calls the generic PostRenamedSchema builder with application/json body +func NewPostRenamedSchemaRequest(server string, body PostRenamedSchemaJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPostRenamedSchemaRequestWithBody(server, "application/json", bodyReader) +} + +// NewPostRenamedSchemaRequestWithBody generates requests for PostRenamedSchema with any type of body +func NewPostRenamedSchemaRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/renamed-schema") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewPatchResourceRequest calls the generic PatchResource builder with application/json body +func NewPatchResourceRequest(server string, id string, body PatchResourceJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPatchResourceRequestWithBody(server, id, "application/json", bodyReader) +} + +// NewPatchResourceRequestWithApplicationJSONPatchPlusJSONBody calls the generic PatchResource builder with application/json-patch+json body +func NewPatchResourceRequestWithApplicationJSONPatchPlusJSONBody(server string, id string, body PatchResourceApplicationJSONPatchPlusJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPatchResourceRequestWithBody(server, id, "application/json-patch+json", bodyReader) +} + +// NewPatchResourceRequestWithApplicationMergePatchPlusJSONBody calls the generic PatchResource builder with application/merge-patch+json body +func NewPatchResourceRequestWithApplicationMergePatchPlusJSONBody(server string, id string, body PatchResourceApplicationMergePatchPlusJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPatchResourceRequestWithBody(server, id, "application/merge-patch+json", bodyReader) +} + +// NewPatchResourceRequestWithBody generates requests for PatchResource with any type of body +func NewPatchResourceRequestWithBody(server string, id string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "id", id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/resources/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PATCH", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewGetStatusRequest generates requests for GetStatus +func NewGetStatusRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/status") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetZapRequest generates requests for GetZap +func NewGetZapRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/zap") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewPostZapRequest calls the generic PostZap builder with application/json body +func NewPostZapRequest(server string, body PostZapJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPostZapRequestWithBody(server, "application/json", bodyReader) +} + +// NewPostZapRequestWithBody generates requests for PostZap with any type of body +func NewPostZapRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/zap") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // ListEntitiesWithResponse request + ListEntitiesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListEntitiesResponse, error) + + // PostFooWithBodyWithResponse request with any body + PostFooWithBodyWithResponse(ctx context.Context, params *PostFooParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostFooResponse, error) + + PostFooWithResponse(ctx context.Context, params *PostFooParams, body PostFooJSONRequestBody, reqEditors ...RequestEditorFn) (*PostFooResponse, error) + + // ListItemsWithResponse request + ListItemsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListItemsResponse2, error) + + // CreateItemWithBodyWithResponse request with any body + CreateItemWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateItemResponse2, error) + + CreateItemWithResponse(ctx context.Context, body CreateItemJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateItemResponse2, error) + + // CreateOrderWithBodyWithResponse request with any body + CreateOrderWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateOrderResponse, error) + + CreateOrderWithResponse(ctx context.Context, body CreateOrderJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateOrderResponse, error) + + CreateOrderWithApplicationJSONPatchPlusJSONBodyWithResponse(ctx context.Context, body CreateOrderApplicationJSONPatchPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateOrderResponse, error) + + CreateOrderWithApplicationMergePatchPlusJSONBodyWithResponse(ctx context.Context, body CreateOrderApplicationMergePatchPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateOrderResponse, error) + + // GetOutcomeWithResponse request + GetOutcomeWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetOutcomeResponse, error) + + // PostOutcomeWithBodyWithResponse request with any body + PostOutcomeWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostOutcomeResponse, error) + + PostOutcomeWithResponse(ctx context.Context, body PostOutcomeJSONRequestBody, reqEditors ...RequestEditorFn) (*PostOutcomeResponse, error) + + // SendPayloadWithBodyWithResponse request with any body + SendPayloadWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SendPayloadResponse, error) + + SendPayloadWithResponse(ctx context.Context, body SendPayloadJSONRequestBody, reqEditors ...RequestEditorFn) (*SendPayloadResponse, error) + + // CreatePetWithBodyWithResponse request with any body + CreatePetWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreatePetResponse, error) + + CreatePetWithResponse(ctx context.Context, body CreatePetJSONRequestBody, reqEditors ...RequestEditorFn) (*CreatePetResponse, error) + + // QueryWithBodyWithResponse request with any body + QueryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*QueryResponse2, error) + + QueryWithResponse(ctx context.Context, body QueryJSONRequestBody, reqEditors ...RequestEditorFn) (*QueryResponse2, error) + + // GetQuxWithResponse request + GetQuxWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetQuxResponse, error) + + // PostQuxWithBodyWithResponse request with any body + PostQuxWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostQuxResponse, error) + + PostQuxWithResponse(ctx context.Context, body PostQuxJSONRequestBody, reqEditors ...RequestEditorFn) (*PostQuxResponse, error) + + // GetRenamedSchemaWithResponse request + GetRenamedSchemaWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetRenamedSchemaResponse, error) + + // PostRenamedSchemaWithBodyWithResponse request with any body + PostRenamedSchemaWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostRenamedSchemaResponse, error) + + PostRenamedSchemaWithResponse(ctx context.Context, body PostRenamedSchemaJSONRequestBody, reqEditors ...RequestEditorFn) (*PostRenamedSchemaResponse, error) + + // PatchResourceWithBodyWithResponse request with any body + PatchResourceWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchResourceResponse, error) + + PatchResourceWithResponse(ctx context.Context, id string, body PatchResourceJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchResourceResponse, error) + + PatchResourceWithApplicationJSONPatchPlusJSONBodyWithResponse(ctx context.Context, id string, body PatchResourceApplicationJSONPatchPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchResourceResponse, error) + + PatchResourceWithApplicationMergePatchPlusJSONBodyWithResponse(ctx context.Context, id string, body PatchResourceApplicationMergePatchPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchResourceResponse, error) + + // GetStatusWithResponse request + GetStatusWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetStatusResponse2, error) + + // GetZapWithResponse request + GetZapWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetZapResponse, error) + + // PostZapWithBodyWithResponse request with any body + PostZapWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostZapResponse, error) + + PostZapWithResponse(ctx context.Context, body PostZapJSONRequestBody, reqEditors ...RequestEditorFn) (*PostZapResponse, error) +} + +type ListEntitiesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *struct { + Data *[]Widget `json:"data,omitempty"` + Metadata *Metadata `json:"metadata,omitempty"` + } +} + +// Status returns HTTPResponse.Status +func (r ListEntitiesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListEntitiesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type PostFooResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *BarResponse +} + +// Status returns HTTPResponse.Status +func (r PostFooResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PostFooResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type ListItemsResponse2 struct { + Body []byte + HTTPResponse *http.Response + JSON200 *ListItemsResponse +} + +// Status returns HTTPResponse.Status +func (r ListItemsResponse2) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListItemsResponse2) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateItemResponse2 struct { + Body []byte + HTTPResponse *http.Response + JSON200 *CreateItemResponse +} + +// Status returns HTTPResponse.Status +func (r CreateItemResponse2) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateItemResponse2) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateOrderResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Order +} + +// Status returns HTTPResponse.Status +func (r CreateOrderResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateOrderResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetOutcomeResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *OutcomeResult +} + +// Status returns HTTPResponse.Status +func (r GetOutcomeResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetOutcomeResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type PostOutcomeResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r PostOutcomeResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PostOutcomeResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type SendPayloadResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Payload +} + +// Status returns HTTPResponse.Status +func (r SendPayloadResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r SendPayloadResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreatePetResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Pet +} + +// Status returns HTTPResponse.Status +func (r CreatePetResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreatePetResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type QueryResponse2 struct { + Body []byte + HTTPResponse *http.Response + JSON200 *QueryResponse +} + +// Status returns HTTPResponse.Status +func (r QueryResponse2) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r QueryResponse2) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetQuxResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *QuxResponse +} + +// Status returns HTTPResponse.Status +func (r GetQuxResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetQuxResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type PostQuxResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r PostQuxResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PostQuxResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetRenamedSchemaResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Renamer +} + +// Status returns HTTPResponse.Status +func (r GetRenamedSchemaResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetRenamedSchemaResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type PostRenamedSchemaResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r PostRenamedSchemaResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PostRenamedSchemaResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type PatchResourceResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *N200ResourcePatchResponseJSONApplicationJSON + ApplicationjsonPatchJSON200 *N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON + ApplicationjsonPatchQueryJSON200 *N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON + ApplicationmergePatchJSON200 *N200ResourcePatchResponseJSON4ApplicationMergePatchPlusJSON +} +type PatchResource200ApplicationJSONPatchPlusJSON1 = []Resource +type PatchResource200ApplicationJSONPatchPlusJSON2 = string +type PatchResource200ApplicationJSONPatchQueryPlusJSON1 = []Resource +type PatchResource200ApplicationJSONPatchQueryPlusJSON2 = string + +// Status returns HTTPResponse.Status +func (r PatchResourceResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PatchResourceResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetStatusResponse2 struct { + Body []byte + HTTPResponse *http.Response + JSON200 *GetStatusResponse +} + +// Status returns HTTPResponse.Status +func (r GetStatusResponse2) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetStatusResponse2) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetZapResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *ZapResponse +} + +// Status returns HTTPResponse.Status +func (r GetZapResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetZapResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type PostZapResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r PostZapResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PostZapResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ListEntitiesWithResponse request returning *ListEntitiesResponse +func (c *ClientWithResponses) ListEntitiesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListEntitiesResponse, error) { + rsp, err := c.ListEntities(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseListEntitiesResponse(rsp) +} + +// PostFooWithBodyWithResponse request with arbitrary body returning *PostFooResponse +func (c *ClientWithResponses) PostFooWithBodyWithResponse(ctx context.Context, params *PostFooParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostFooResponse, error) { + rsp, err := c.PostFooWithBody(ctx, params, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostFooResponse(rsp) +} + +func (c *ClientWithResponses) PostFooWithResponse(ctx context.Context, params *PostFooParams, body PostFooJSONRequestBody, reqEditors ...RequestEditorFn) (*PostFooResponse, error) { + rsp, err := c.PostFoo(ctx, params, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostFooResponse(rsp) +} + +// ListItemsWithResponse request returning *ListItemsResponse2 +func (c *ClientWithResponses) ListItemsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListItemsResponse2, error) { + rsp, err := c.ListItems(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseListItemsResponse2(rsp) +} + +// CreateItemWithBodyWithResponse request with arbitrary body returning *CreateItemResponse2 +func (c *ClientWithResponses) CreateItemWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateItemResponse2, error) { + rsp, err := c.CreateItemWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateItemResponse2(rsp) +} + +func (c *ClientWithResponses) CreateItemWithResponse(ctx context.Context, body CreateItemJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateItemResponse2, error) { + rsp, err := c.CreateItem(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateItemResponse2(rsp) +} + +// CreateOrderWithBodyWithResponse request with arbitrary body returning *CreateOrderResponse +func (c *ClientWithResponses) CreateOrderWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateOrderResponse, error) { + rsp, err := c.CreateOrderWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateOrderResponse(rsp) +} + +func (c *ClientWithResponses) CreateOrderWithResponse(ctx context.Context, body CreateOrderJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateOrderResponse, error) { + rsp, err := c.CreateOrder(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateOrderResponse(rsp) +} + +func (c *ClientWithResponses) CreateOrderWithApplicationJSONPatchPlusJSONBodyWithResponse(ctx context.Context, body CreateOrderApplicationJSONPatchPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateOrderResponse, error) { + rsp, err := c.CreateOrderWithApplicationJSONPatchPlusJSONBody(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateOrderResponse(rsp) +} + +func (c *ClientWithResponses) CreateOrderWithApplicationMergePatchPlusJSONBodyWithResponse(ctx context.Context, body CreateOrderApplicationMergePatchPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateOrderResponse, error) { + rsp, err := c.CreateOrderWithApplicationMergePatchPlusJSONBody(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateOrderResponse(rsp) +} + +// GetOutcomeWithResponse request returning *GetOutcomeResponse +func (c *ClientWithResponses) GetOutcomeWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetOutcomeResponse, error) { + rsp, err := c.GetOutcome(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetOutcomeResponse(rsp) +} + +// PostOutcomeWithBodyWithResponse request with arbitrary body returning *PostOutcomeResponse +func (c *ClientWithResponses) PostOutcomeWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostOutcomeResponse, error) { + rsp, err := c.PostOutcomeWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostOutcomeResponse(rsp) +} + +func (c *ClientWithResponses) PostOutcomeWithResponse(ctx context.Context, body PostOutcomeJSONRequestBody, reqEditors ...RequestEditorFn) (*PostOutcomeResponse, error) { + rsp, err := c.PostOutcome(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostOutcomeResponse(rsp) +} + +// SendPayloadWithBodyWithResponse request with arbitrary body returning *SendPayloadResponse +func (c *ClientWithResponses) SendPayloadWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SendPayloadResponse, error) { + rsp, err := c.SendPayloadWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseSendPayloadResponse(rsp) +} + +func (c *ClientWithResponses) SendPayloadWithResponse(ctx context.Context, body SendPayloadJSONRequestBody, reqEditors ...RequestEditorFn) (*SendPayloadResponse, error) { + rsp, err := c.SendPayload(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseSendPayloadResponse(rsp) +} + +// CreatePetWithBodyWithResponse request with arbitrary body returning *CreatePetResponse +func (c *ClientWithResponses) CreatePetWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreatePetResponse, error) { + rsp, err := c.CreatePetWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreatePetResponse(rsp) +} + +func (c *ClientWithResponses) CreatePetWithResponse(ctx context.Context, body CreatePetJSONRequestBody, reqEditors ...RequestEditorFn) (*CreatePetResponse, error) { + rsp, err := c.CreatePet(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreatePetResponse(rsp) +} + +// QueryWithBodyWithResponse request with arbitrary body returning *QueryResponse2 +func (c *ClientWithResponses) QueryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*QueryResponse2, error) { + rsp, err := c.QueryWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseQueryResponse2(rsp) +} + +func (c *ClientWithResponses) QueryWithResponse(ctx context.Context, body QueryJSONRequestBody, reqEditors ...RequestEditorFn) (*QueryResponse2, error) { + rsp, err := c.Query(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseQueryResponse2(rsp) +} + +// GetQuxWithResponse request returning *GetQuxResponse +func (c *ClientWithResponses) GetQuxWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetQuxResponse, error) { + rsp, err := c.GetQux(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetQuxResponse(rsp) +} + +// PostQuxWithBodyWithResponse request with arbitrary body returning *PostQuxResponse +func (c *ClientWithResponses) PostQuxWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostQuxResponse, error) { + rsp, err := c.PostQuxWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostQuxResponse(rsp) +} + +func (c *ClientWithResponses) PostQuxWithResponse(ctx context.Context, body PostQuxJSONRequestBody, reqEditors ...RequestEditorFn) (*PostQuxResponse, error) { + rsp, err := c.PostQux(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostQuxResponse(rsp) +} + +// GetRenamedSchemaWithResponse request returning *GetRenamedSchemaResponse +func (c *ClientWithResponses) GetRenamedSchemaWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetRenamedSchemaResponse, error) { + rsp, err := c.GetRenamedSchema(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetRenamedSchemaResponse(rsp) +} + +// PostRenamedSchemaWithBodyWithResponse request with arbitrary body returning *PostRenamedSchemaResponse +func (c *ClientWithResponses) PostRenamedSchemaWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostRenamedSchemaResponse, error) { + rsp, err := c.PostRenamedSchemaWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostRenamedSchemaResponse(rsp) +} + +func (c *ClientWithResponses) PostRenamedSchemaWithResponse(ctx context.Context, body PostRenamedSchemaJSONRequestBody, reqEditors ...RequestEditorFn) (*PostRenamedSchemaResponse, error) { + rsp, err := c.PostRenamedSchema(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostRenamedSchemaResponse(rsp) +} + +// PatchResourceWithBodyWithResponse request with arbitrary body returning *PatchResourceResponse +func (c *ClientWithResponses) PatchResourceWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchResourceResponse, error) { + rsp, err := c.PatchResourceWithBody(ctx, id, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePatchResourceResponse(rsp) +} + +func (c *ClientWithResponses) PatchResourceWithResponse(ctx context.Context, id string, body PatchResourceJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchResourceResponse, error) { + rsp, err := c.PatchResource(ctx, id, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePatchResourceResponse(rsp) +} + +func (c *ClientWithResponses) PatchResourceWithApplicationJSONPatchPlusJSONBodyWithResponse(ctx context.Context, id string, body PatchResourceApplicationJSONPatchPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchResourceResponse, error) { + rsp, err := c.PatchResourceWithApplicationJSONPatchPlusJSONBody(ctx, id, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePatchResourceResponse(rsp) +} + +func (c *ClientWithResponses) PatchResourceWithApplicationMergePatchPlusJSONBodyWithResponse(ctx context.Context, id string, body PatchResourceApplicationMergePatchPlusJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchResourceResponse, error) { + rsp, err := c.PatchResourceWithApplicationMergePatchPlusJSONBody(ctx, id, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePatchResourceResponse(rsp) +} + +// GetStatusWithResponse request returning *GetStatusResponse2 +func (c *ClientWithResponses) GetStatusWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetStatusResponse2, error) { + rsp, err := c.GetStatus(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetStatusResponse2(rsp) +} + +// GetZapWithResponse request returning *GetZapResponse +func (c *ClientWithResponses) GetZapWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetZapResponse, error) { + rsp, err := c.GetZap(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetZapResponse(rsp) +} + +// PostZapWithBodyWithResponse request with arbitrary body returning *PostZapResponse +func (c *ClientWithResponses) PostZapWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostZapResponse, error) { + rsp, err := c.PostZapWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostZapResponse(rsp) +} + +func (c *ClientWithResponses) PostZapWithResponse(ctx context.Context, body PostZapJSONRequestBody, reqEditors ...RequestEditorFn) (*PostZapResponse, error) { + rsp, err := c.PostZap(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostZapResponse(rsp) +} + +// ParseListEntitiesResponse parses an HTTP response from a ListEntitiesWithResponse call +func ParseListEntitiesResponse(rsp *http.Response) (*ListEntitiesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListEntitiesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest struct { + Data *[]Widget `json:"data,omitempty"` + Metadata *Metadata `json:"metadata,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParsePostFooResponse parses an HTTP response from a PostFooWithResponse call +func ParsePostFooResponse(rsp *http.Response) (*PostFooResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PostFooResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest BarResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseListItemsResponse2 parses an HTTP response from a ListItemsWithResponse call +func ParseListItemsResponse2(rsp *http.Response) (*ListItemsResponse2, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListItemsResponse2{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest ListItemsResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseCreateItemResponse2 parses an HTTP response from a CreateItemWithResponse call +func ParseCreateItemResponse2(rsp *http.Response) (*CreateItemResponse2, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateItemResponse2{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest CreateItemResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseCreateOrderResponse parses an HTTP response from a CreateOrderWithResponse call +func ParseCreateOrderResponse(rsp *http.Response) (*CreateOrderResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateOrderResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Order + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseGetOutcomeResponse parses an HTTP response from a GetOutcomeWithResponse call +func ParseGetOutcomeResponse(rsp *http.Response) (*GetOutcomeResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetOutcomeResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest OutcomeResult + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParsePostOutcomeResponse parses an HTTP response from a PostOutcomeWithResponse call +func ParsePostOutcomeResponse(rsp *http.Response) (*PostOutcomeResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PostOutcomeResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseSendPayloadResponse parses an HTTP response from a SendPayloadWithResponse call +func ParseSendPayloadResponse(rsp *http.Response) (*SendPayloadResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &SendPayloadResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Payload + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseCreatePetResponse parses an HTTP response from a CreatePetWithResponse call +func ParseCreatePetResponse(rsp *http.Response) (*CreatePetResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreatePetResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Pet + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseQueryResponse2 parses an HTTP response from a QueryWithResponse call +func ParseQueryResponse2(rsp *http.Response) (*QueryResponse2, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &QueryResponse2{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest QueryResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseGetQuxResponse parses an HTTP response from a GetQuxWithResponse call +func ParseGetQuxResponse(rsp *http.Response) (*GetQuxResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetQuxResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest QuxResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParsePostQuxResponse parses an HTTP response from a PostQuxWithResponse call +func ParsePostQuxResponse(rsp *http.Response) (*PostQuxResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PostQuxResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetRenamedSchemaResponse parses an HTTP response from a GetRenamedSchemaWithResponse call +func ParseGetRenamedSchemaResponse(rsp *http.Response) (*GetRenamedSchemaResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetRenamedSchemaResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Renamer + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParsePostRenamedSchemaResponse parses an HTTP response from a PostRenamedSchemaWithResponse call +func ParsePostRenamedSchemaResponse(rsp *http.Response) (*PostRenamedSchemaResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PostRenamedSchemaResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParsePatchResourceResponse parses an HTTP response from a PatchResourceWithResponse call +func ParsePatchResourceResponse(rsp *http.Response) (*PatchResourceResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PatchResourceResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case rsp.Header.Get("Content-Type") == "application/json-patch+json" && rsp.StatusCode == 200: + var dest N200ResourcePatchResponseJSON2ApplicationJSONPatchPlusJSON + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.ApplicationjsonPatchJSON200 = &dest + + case rsp.Header.Get("Content-Type") == "application/json-patch-query+json" && rsp.StatusCode == 200: + var dest N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.ApplicationjsonPatchQueryJSON200 = &dest + + case rsp.Header.Get("Content-Type") == "application/json" && rsp.StatusCode == 200: + var dest N200ResourcePatchResponseJSONApplicationJSON + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case rsp.Header.Get("Content-Type") == "application/merge-patch+json" && rsp.StatusCode == 200: + var dest N200ResourcePatchResponseJSON4ApplicationMergePatchPlusJSON + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.ApplicationmergePatchJSON200 = &dest + + } + + return response, nil +} + +// ParseGetStatusResponse2 parses an HTTP response from a GetStatusWithResponse call +func ParseGetStatusResponse2(rsp *http.Response) (*GetStatusResponse2, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetStatusResponse2{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest GetStatusResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseGetZapResponse parses an HTTP response from a GetZapWithResponse call +func ParseGetZapResponse(rsp *http.Response) (*GetZapResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetZapResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest ZapResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParsePostZapResponse parses an HTTP response from a PostZapWithResponse call +func ParsePostZapResponse(rsp *http.Response) (*PostZapResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PostZapResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} diff --git a/internal/test/name_conflict_resolution/name_conflict_resolution_test.go b/internal/test/name_conflict_resolution/name_conflict_resolution_test.go new file mode 100644 index 0000000000..158f56f3f4 --- /dev/null +++ b/internal/test/name_conflict_resolution/name_conflict_resolution_test.go @@ -0,0 +1,460 @@ +package nameconflictresolution + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestCrossSectionCollisions verifies Pattern A: when the same name "Bar" +// appears in schemas, parameters, requestBodies, and responses, the resolver +// keeps the bare name for the component schema and suffixes the others. +// +// Covers issues: #200, #254, #407, #1881, PR #292 +func TestCrossSectionCollisions(t *testing.T) { + // Schema type keeps bare name "Bar" + bar := Bar{Value: ptr("hello")} + assert.Equal(t, "hello", *bar.Value) + + // No collision for Bar2 + bar2 := Bar2{Value: ptr(float32(1.5))} + assert.Equal(t, float32(1.5), *bar2.Value) + + // Parameter type gets "Parameter" suffix + param := BarParameter("query-value") + assert.Equal(t, "query-value", string(param)) + + // RequestBody type gets "RequestBody" suffix + reqBody := BarRequestBody{Value: ptr(42)} + assert.Equal(t, 42, *reqBody.Value) + + // Response type gets "Response" suffix + resp := BarResponse{ + Value1: &Bar{Value: ptr("v1")}, + Value2: &Bar2{Value: ptr(float32(2.0))}, + } + assert.Equal(t, "v1", *resp.Value1.Value) + assert.Equal(t, float32(2.0), *resp.Value2.Value) + + // PostFoo wrapper does not collide (unique name PostFooResponse) + var wrapper PostFooResponse + assert.Nil(t, wrapper.JSON200) + // JSON200 field points to the response type BarResponse (not schema type Bar) + wrapper.JSON200 = &resp + assert.Equal(t, "v1", *wrapper.JSON200.Value1.Value) +} + +// TestSchemaVsClientWrapper verifies Pattern B: schema "CreateItemResponse" +// collides with the client wrapper for operation "createItem". The schema +// keeps the bare name; the wrapper gets numeric fallback "CreateItemResponse2". +// +// Covers issues: #1474, #1713, #1450 +func TestSchemaVsClientWrapper(t *testing.T) { + // Schema type keeps bare name + schema := CreateItemResponse{ + Id: ptr("item-1"), + Name: ptr("Widget"), + } + assert.Equal(t, "item-1", *schema.Id) + assert.Equal(t, "Widget", *schema.Name) + + // Client wrapper gets numeric fallback + var wrapper CreateItemResponse2 + assert.Nil(t, wrapper.Body) + assert.Nil(t, wrapper.HTTPResponse) + assert.Nil(t, wrapper.JSON200) + + // JSON200 field references the schema type, not the wrapper itself + wrapper.JSON200 = &schema + assert.Equal(t, "item-1", *wrapper.JSON200.Id) +} + +// TestSchemaAliasVsClientWrapper verifies Pattern C: schema "ListItemsResponse" +// (a string alias) collides with the client wrapper for operation "listItems". +// The schema keeps the bare name; the wrapper gets "ListItemsResponse2". +// +// Covers issue: #1357 +func TestSchemaAliasVsClientWrapper(t *testing.T) { + // Schema type is a string alias + var schema ListItemsResponse = "item-list" + assert.Equal(t, "item-list", schema) + + // Client wrapper gets numeric fallback + var wrapper ListItemsResponse2 + assert.Nil(t, wrapper.Body) + assert.Nil(t, wrapper.HTTPResponse) + assert.Nil(t, wrapper.JSON200) + + // JSON200 field references the schema type (string alias) + wrapper.JSON200 = &schema + assert.Equal(t, "item-list", *wrapper.JSON200) +} + +// TestOperationNameMatchesSchema verifies Pattern D: schema "QueryResponse" +// collides with the client wrapper for operation "query" (which generates +// "QueryResponse"). The schema keeps the bare name; the wrapper gets +// "QueryResponse2". +// +// Covers issue: #255 +func TestOperationNameMatchesSchema(t *testing.T) { + // Schema type keeps bare name + schema := QueryResponse{ + Results: &[]string{"result1", "result2"}, + } + assert.Len(t, *schema.Results, 2) + + // Client wrapper gets numeric fallback + var wrapper QueryResponse2 + assert.Nil(t, wrapper.JSON200) + + // JSON200 field references the schema type + wrapper.JSON200 = &schema + assert.Len(t, *wrapper.JSON200.Results, 2) +} + +// TestSchemaMatchesOpResponse verifies Pattern E: schema "GetStatusResponse" +// collides with the client wrapper for operation "getStatus" (which generates +// "GetStatusResponse"). The schema keeps the bare name; the wrapper gets +// "GetStatusResponse2". +// +// Covers issues: #2097, #899 +func TestSchemaMatchesOpResponse(t *testing.T) { + // Schema type keeps bare name + schema := GetStatusResponse{ + Status: ptr("healthy"), + Timestamp: ptr("2025-01-01T00:00:00Z"), + } + assert.Equal(t, "healthy", *schema.Status) + assert.Equal(t, "2025-01-01T00:00:00Z", *schema.Timestamp) + + // Client wrapper gets numeric fallback + var wrapper GetStatusResponse2 + assert.Nil(t, wrapper.JSON200) + + // JSON200 field references the schema type + wrapper.JSON200 = &schema + assert.Equal(t, "healthy", *wrapper.JSON200.Status) +} + +// TestMultipleJsonContentTypes verifies Pattern H: schema "Order" collides with +// requestBody "Order" which has 3 content types that all contain "json": +// - application/json +// - application/merge-patch+json +// - application/json-patch+json +// +// All three map to the same "JSON" short name via contentTypeSuffix(), which +// would trigger an infinite oscillation between context suffix ("RequestBody") +// and content type suffix ("JSON") strategies if applied per-group. The global +// phase approach lets numeric fallback break the cycle. +// +// Expected types: +// - Order struct (schema keeps bare name) +// - OrderRequestBodyJSON struct (application/json requestBody) +// - OrderRequestBodyJSON2 []struct (application/json-patch+json, numeric fallback) +// - OrderRequestBodyJSON3 struct (application/merge-patch+json, numeric fallback) +// +// Covers: PR #2213 (multiple JSON content types in requestBody) +func TestMultipleJsonContentTypes(t *testing.T) { + // Schema type keeps bare name "Order" + order := Order{ + Id: ptr("order-1"), + Product: ptr("Widget"), + } + assert.Equal(t, "order-1", *order.Id) + assert.Equal(t, "Widget", *order.Product) + + // The 3 requestBody content types should each get a unique name. + // They all collide on "OrderRequestBodyJSON", so numeric fallback kicks in. + // The type names below are compile-time assertions that all 3 exist and are distinct. + + // application/json requestBody + jsonBody := OrderRequestBodyJSON{ + Id: ptr("order-2"), + Product: ptr("Gadget"), + } + assert.Equal(t, "order-2", *jsonBody.Id) + + // application/json-patch+json requestBody (numeric fallback, array type alias) + var jsonPatch OrderRequestBodyJSON2 + assert.Nil(t, jsonPatch) + + // application/merge-patch+json requestBody (numeric fallback) + mergePatch := OrderRequestBodyJSON3{ + Product: ptr("Gadget-patched"), + } + assert.Equal(t, "Gadget-patched", *mergePatch.Product) + + // CreateOrder wrapper should not collide + var wrapper CreateOrderResponse + assert.Nil(t, wrapper.JSON200) + wrapper.JSON200 = &order + assert.Equal(t, "order-1", *wrapper.JSON200.Id) +} + +// TestRequestBodyVsSchema verifies that "Pet" in schemas and requestBodies +// resolves correctly: the schema keeps bare name "Pet", the requestBody +// gets "PetRequestBody". +// +// Covers issues: #254, #407 +func TestRequestBodyVsSchema(t *testing.T) { + // Schema type keeps bare name + pet := Pet{ + Id: ptr(1), + Name: ptr("Fluffy"), + } + assert.Equal(t, 1, *pet.Id) + assert.Equal(t, "Fluffy", *pet.Name) + + // RequestBody type gets "RequestBody" suffix + petReqBody := PetRequestBody{ + Name: ptr("Fluffy"), + Species: ptr("cat"), + } + assert.Equal(t, "Fluffy", *petReqBody.Name) + assert.Equal(t, "cat", *petReqBody.Species) + + // CreatePet wrapper doesn't collide (unique name CreatePetResponse) + var wrapper CreatePetResponse + assert.Nil(t, wrapper.JSON200) + + // JSON200 field references the schema type Pet + wrapper.JSON200 = &pet + assert.Equal(t, "Fluffy", *wrapper.JSON200.Name) +} + +// TestRefTargetPicksUpRename verifies that when an operation references a +// renamed component via $ref, the generated wrapper type uses the resolved +// (renamed) type, not the original spec name. +func TestRefTargetPicksUpRename(t *testing.T) { + // When postFoo references $ref: '#/components/responses/Bar', + // and response Bar is renamed to BarResponse, the wrapper's + // JSON200 field must use BarResponse (not Bar). + barResp := BarResponse{ + Value1: &Bar{Value: ptr("v1")}, + Value2: &Bar2{Value: ptr(float32(2.0))}, + } + var wrapper PostFooResponse + wrapper.JSON200 = &barResp // compile-time: JSON200 must be *BarResponse + assert.Equal(t, "v1", *wrapper.JSON200.Value1.Value) + assert.Equal(t, float32(2.0), *wrapper.JSON200.Value2.Value) +} + +// TestExtGoTypeNameWithCollisionResolver verifies that when a component schema +// has x-go-type-name: CustomQux and collides with a response "Qux", the +// collision resolver controls the top-level Go type names while x-go-type-name +// controls the underlying type definition. +// +// Expected types: +// - CustomQux struct (underlying type from x-go-type-name) +// - Qux = CustomQux (schema keeps bare name, aliased) +// - QuxResponse struct (response gets suffixed) +func TestExtGoTypeNameWithCollisionResolver(t *testing.T) { + // CustomQux is the underlying struct created by x-go-type-name + custom := CustomQux{Label: ptr("hello")} + assert.Equal(t, "hello", *custom.Label) + + // Qux is a type alias for CustomQux (schema keeps bare name) + var qux Qux = custom //nolint:staticcheck // explicit type needed to prove Qux aliases CustomQux + assert.Equal(t, "hello", *qux.Label) + + // QuxResponse is the response type (response gets suffixed) + quxResp := QuxResponse{Data: ptr("response-data")} + assert.Equal(t, "response-data", *quxResp.Data) + + // GetQuxResponse client wrapper's JSON200 field uses *QuxResponse + var wrapper GetQuxResponse + assert.Nil(t, wrapper.JSON200) + wrapper.JSON200 = &quxResp + assert.Equal(t, "response-data", *wrapper.JSON200.Data) +} + +// TestExtGoTypeWithCollisionResolver verifies that when a component schema has +// x-go-type: string and collides with a response "Zap", the collision resolver +// controls the top-level Go type names while x-go-type controls the target type. +// +// Expected types: +// - Zap = string (schema keeps bare name, x-go-type controls target) +// - ZapResponse struct (response gets suffixed) +func TestExtGoTypeWithCollisionResolver(t *testing.T) { + // Zap is a string type alias (x-go-type controls the target) + var zap Zap = "test-value" + assert.Equal(t, "test-value", string(zap)) + + // ZapResponse is the response type (response gets suffixed) + zapResp := ZapResponse{Result: ptr("response-result")} + assert.Equal(t, "response-result", *zapResp.Result) + + // GetZapResponse client wrapper's JSON200 field uses *ZapResponse + var wrapper GetZapResponse + assert.Nil(t, wrapper.JSON200) + wrapper.JSON200 = &zapResp + assert.Equal(t, "response-result", *wrapper.JSON200.Result) +} + +// TestInlineResponseWithRefProperties verifies Pattern I (oapi-codegen-exp#14): +// when a response has an inline object whose properties contain $refs to component +// schemas with x-go-type, the property-level refs must NOT produce duplicate type +// declarations. The component schemas keep their type aliases (Widget = string, +// Metadata = string), and the inline response object gets its own struct type. +// +// Covers: oapi-codegen-exp#14 +func TestInlineResponseWithRefProperties(t *testing.T) { + // Component schemas with x-go-type: string produce type aliases + var widget Widget = "widget-value" + assert.Equal(t, "widget-value", string(widget)) + + var metadata Metadata = "metadata-value" + assert.Equal(t, "metadata-value", string(metadata)) + + // The inline response object should have fields typed by the component aliases. + // The client wrapper for listEntities should exist and have a JSON200 field + // pointing to the inline response type. + var wrapper ListEntitiesResponse + assert.Nil(t, wrapper.JSON200) +} + +// TestDuplicateOneOfMembersAcrossContentTypes verifies Pattern J: +// when a response has multiple JSON content types (e.g., application/json-patch+json +// and application/json-patch-query+json) that share an identical oneOf schema with +// inline (non-$ref) members, the codegen must not emit duplicate type declarations +// for those inline members. +// +// Additionally, when a requestBody shares its name with a component schema and its +// content schemas $ref the component schema (plus one $refs a different schema), +// the collision resolver must assign unique names. +// +// Expected types: +// - ResourceMVO struct (schema keeps bare name) +// - Resource struct (no collision) +// - JsonPatch []struct (no collision) +// - ResourceMVORequestBodyJSON = ResourceMVO (requestBody application/json) +// - ResourceMVORequestBodyJSON2 = JsonPatch (requestBody application/json-patch+json) +// - ResourceMVORequestBodyJSON3 = ResourceMVO (requestBody application/merge-patch+json) +// - PatchResourceResponse struct (client response wrapper) +// - inline oneOf member types (must not be duplicated) +func TestDuplicateOneOfMembersAcrossContentTypes(t *testing.T) { + // Schema types keep bare names + resource := Resource{ + Id: ptr("res-1"), + Name: ptr("MyResource"), + Status: ptr("active"), + } + assert.Equal(t, "res-1", *resource.Id) + + resourceMVO := ResourceMVO{ + Name: ptr("MyResource"), + Status: ptr("active"), + } + assert.Equal(t, "MyResource", *resourceMVO.Name) + + // RequestBody collision resolution: schema "Resource_MVO" keeps bare name, + // requestBody content types get suffixed. + var reqBodyJSON ResourceMVORequestBodyJSON + reqBodyJSON.Name = ptr("test") + assert.Equal(t, "test", *reqBodyJSON.Name) + + var reqBodyPatch ResourceMVORequestBodyJSON2 + assert.Nil(t, reqBodyPatch) // JsonPatch alias (slice type) + + var reqBodyMerge ResourceMVORequestBodyJSON3 + reqBodyMerge.Name = ptr("merge") + assert.Equal(t, "merge", *reqBodyMerge.Name) + + // Client response wrapper should exist. The primary assertion here + // is that the package compiles — no duplicate oneOf member types and + // no undefined response type names. + var wrapper PatchResourceResponse + assert.Nil(t, wrapper.Body) + assert.Nil(t, wrapper.HTTPResponse) +} + +// TestXGoNameOnSchemaPreserved verifies Pattern K: when a component schema +// has x-go-name, the collision resolver must use the x-go-name value as the +// schema's type name (pinned), not the original spec name. +// +// Schema "Renamer" has x-go-name: "SpecialName" and shares a name with +// response "Renamer". With correct x-go-name handling the schema becomes +// "SpecialName", so no collision exists and the response keeps "Renamer". +// +// Expected types: +// - SpecialName struct (schema "Renamer" pinned by x-go-name) +// - Renamer struct (response "Renamer" — no collision) +// +// Covers: PR #2213 review finding (x-go-name not respected by resolver) +func TestXGoNameOnSchemaPreserved(t *testing.T) { + // Schema "Renamer" should use its x-go-name "SpecialName" + schema := SpecialName{Label: ptr("test-label")} + assert.Equal(t, "test-label", *schema.Label) + + // Response "Renamer" should keep its bare name (no collision with schema) + resp := Renamer{Data: ptr("response-data")} + assert.Equal(t, "response-data", *resp.Data) + + // Client wrapper for getRenamedSchema should reference the response type + var wrapper GetRenamedSchemaResponse + assert.Nil(t, wrapper.JSON200) + wrapper.JSON200 = &resp + assert.Equal(t, "response-data", *wrapper.JSON200.Data) +} + +// TestXGoNameOnResponsePreserved verifies Pattern L: when a component response +// has x-go-name, the collision resolver must use the x-go-name value as the +// response's type name (pinned), not the original spec name. +// +// Response "Outcome" has x-go-name: "OutcomeResult" and shares a name with +// schema "Outcome". With correct x-go-name handling the response becomes +// "OutcomeResult", so no collision exists and the schema keeps "Outcome". +// +// Expected types: +// - Outcome struct (schema keeps bare name — no collision) +// - OutcomeResult struct (response "Outcome" pinned by x-go-name) +// +// Covers: PR #2213 review finding (x-go-name not respected by resolver) +func TestXGoNameOnResponsePreserved(t *testing.T) { + // Schema "Outcome" should keep its bare name + schema := Outcome{Value: ptr("some-value")} + assert.Equal(t, "some-value", *schema.Value) + + // Response "Outcome" should use its x-go-name "OutcomeResult" + resp := OutcomeResult{Result: ptr("outcome-data")} + assert.Equal(t, "outcome-data", *resp.Result) + + // Client wrapper for getOutcome should reference the response type + var wrapper GetOutcomeResponse + assert.Nil(t, wrapper.JSON200) + wrapper.JSON200 = &resp + assert.Equal(t, "outcome-data", *wrapper.JSON200.Result) +} + +// TestXGoNameOnRequestBodyPreserved verifies Pattern M: when a component +// requestBody has x-go-name, the collision resolver must use the x-go-name +// value as the requestBody's type name (pinned), not the original spec name. +// +// RequestBody "Payload" has x-go-name: "PayloadBody" and shares a name with +// schema "Payload". With correct x-go-name handling the requestBody becomes +// "PayloadBody", so no collision exists and the schema keeps "Payload". +// +// Expected types: +// - Payload struct (schema keeps bare name — no collision) +// - PayloadBody struct (requestBody "Payload" pinned by x-go-name) +// +// Covers: PR #2213 review finding (x-go-name not respected by resolver) +func TestXGoNameOnRequestBodyPreserved(t *testing.T) { + // Schema "Payload" should keep its bare name + schema := Payload{Content: ptr("payload-content")} + assert.Equal(t, "payload-content", *schema.Content) + + // RequestBody "Payload" should use its x-go-name "PayloadBody" + reqBody := PayloadBody{Data: ptr("body-data")} + assert.Equal(t, "body-data", *reqBody.Data) + + // Client wrapper for sendPayload should reference the schema type + var wrapper SendPayloadResponse + assert.Nil(t, wrapper.JSON200) + wrapper.JSON200 = &schema + assert.Equal(t, "payload-content", *wrapper.JSON200.Content) +} + +func ptr[T any](v T) *T { + return &v +} diff --git a/internal/test/name_conflict_resolution/spec.yaml b/internal/test/name_conflict_resolution/spec.yaml new file mode 100644 index 0000000000..6cebbb9668 --- /dev/null +++ b/internal/test/name_conflict_resolution/spec.yaml @@ -0,0 +1,607 @@ +openapi: 3.0.1 + +info: + title: "Comprehensive name collision resolution test" + description: | + Exercises all documented name collision patterns across issues and PRs: + #200, #254, #255, #292, #407, #899, #1357, #1450, #1474, #1713, #1881, #2097, #2213 + Also covers oapi-codegen-exp#14 (inline response object with $ref properties). + Patterns K/L/M cover x-go-name preservation during collision resolution (PR #2213 review). + version: 0.0.0 + +paths: + # Pattern A: Cross-section collision (issues #200, #254, #407, #1881, PR #292) + # "Bar" appears in schemas, parameters, requestBodies, responses, and headers. + /foo: + post: + operationId: postFoo + parameters: + - $ref: '#/components/parameters/Bar' + requestBody: + $ref: '#/components/requestBodies/Bar' + responses: + 200: + $ref: '#/components/responses/Bar' + + # Pattern B: Schema vs client wrapper (issues #1474, #1713, #1450) + # Schema "CreateItemResponse" collides with createItem wrapper. + /items: + post: + operationId: createItem + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CreateItemResponse' + + # Pattern C: Schema alias vs client wrapper (issue #1357) + # Schema "ListItemsResponse" (string alias) collides with listItems wrapper. + get: + operationId: listItems + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListItemsResponse' + + # Pattern D: Operation name = schema response name (issue #255) + # Schema "QueryResponse" collides with query wrapper. + /query: + post: + operationId: query + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + q: + type: string + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QueryResponse' + + # Pattern E: Schema matches op+Response (issues #2097, #899) + # Schema "GetStatusResponse" collides with getStatus wrapper. + /status: + get: + operationId: getStatus + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GetStatusResponse' + + # Pattern F: x-go-type-name extension + cross-section collision + # Schema "Qux" has x-go-type-name and collides with response "Qux". + /qux: + get: + operationId: getQux + responses: + '200': + $ref: '#/components/responses/Qux' + post: + operationId: postQux + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Qux' + responses: + '200': + description: OK + + # Pattern G: x-go-type extension + cross-section collision + # Schema "Zap" has x-go-type and collides with response "Zap". + /zap: + get: + operationId: getZap + responses: + '200': + $ref: '#/components/responses/Zap' + post: + operationId: postZap + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Zap' + responses: + '200': + description: OK + + # Pattern H: Multiple JSON content types in requestBody (PR #2213) + # "Order" appears in schemas and requestBodies. The requestBody has 3 content + # types that all contain "json" and collapse to the same "JSON" short name: + # application/json, application/merge-patch+json, application/json-patch+json + # This triggers an infinite oscillation between context suffix and content type + # suffix strategies unless the numeric fallback can break the cycle. + /orders: + post: + operationId: createOrder + requestBody: + $ref: '#/components/requestBodies/Order' + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + + # Pattern I: Inline response object with $ref properties to x-go-type schemas + # (oapi-codegen-exp#14). The response has an inline object with properties that + # $ref component schemas carrying x-go-type. Each property ref should use the + # component schema's type alias, not produce duplicate type declarations. + /entities: + get: + operationId: listEntities + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Widget' + metadata: + $ref: '#/components/schemas/Metadata' + + # Pattern J: Duplicate inline oneOf members across response content types + # A PATCH operation returns multiple JSON content types + # (application/json, application/json-patch+json, application/json-patch-query+json, + # application/merge-patch+json). The json-patch and json-patch-query variants + # share an identical oneOf schema with inline (non-$ref) members. The codegen + # must not emit duplicate type declarations for those inline members. + # + # Additionally, the requestBody shares the same name as a component schema + # ("Resource_MVO") where the requestBody content schemas $ref the component + # schema, and one content type $refs a different schema. + /resources/{id}: + patch: + operationId: patchResource + parameters: + - name: id + in: path + required: true + schema: + type: string + requestBody: + $ref: '#/components/requestBodies/Resource_MVO' + responses: + '200': + $ref: '#/components/responses/200Resource_Patch' + + # Pattern K: x-go-name on schema — resolver must preserve user-specified names + # Schema "Renamer" has x-go-name: "SpecialName". Response "Renamer" also exists. + # The resolver should use "SpecialName" for the schema (pinned by x-go-name) + # and "Renamer" for the response (no collision since the schema is "SpecialName"). + # Covers: PR #2213 review finding (x-go-name not respected by resolver) + /renamed-schema: + get: + operationId: getRenamedSchema + responses: + '200': + $ref: '#/components/responses/Renamer' + post: + operationId: postRenamedSchema + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Renamer' + responses: + '200': + description: OK + + # Pattern L: x-go-name on response — resolver must preserve user-specified names + # Response "Outcome" has x-go-name: "OutcomeResult". Schema "Outcome" also exists. + # The resolver should use "OutcomeResult" for the response (pinned by x-go-name) + # and "Outcome" for the schema (no collision since the response is "OutcomeResult"). + # Covers: PR #2213 review finding (x-go-name not respected by resolver) + /outcome: + get: + operationId: getOutcome + responses: + '200': + $ref: '#/components/responses/Outcome' + post: + operationId: postOutcome + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Outcome' + responses: + '200': + description: OK + + # Pattern M: x-go-name on requestBody — resolver must preserve user-specified names + # RequestBody "Payload" has x-go-name: "PayloadBody". Schema "Payload" also exists. + # The resolver should use "PayloadBody" for the requestBody (pinned by x-go-name) + # and "Payload" for the schema (no collision since the requestBody is "PayloadBody"). + # Covers: PR #2213 review finding (x-go-name not respected by resolver) + /payload: + post: + operationId: sendPayload + requestBody: + $ref: '#/components/requestBodies/Payload' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Payload' + + # Cross-section: requestBody vs schema (issues #254, #407) + # "Pet" appears in both schemas and requestBodies. + /pets: + post: + operationId: createPet + requestBody: + $ref: '#/components/requestBodies/Pet' + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + +components: + schemas: + Bar: + type: object + properties: + value: + type: string + + Bar2: + type: object + properties: + value: + type: number + + CreateItemResponse: + type: object + properties: + id: + type: string + name: + type: string + + ListItemsResponse: + type: string + + QueryResponse: + type: object + properties: + results: + type: array + items: + type: string + + GetStatusResponse: + type: object + properties: + status: + type: string + timestamp: + type: string + + # Pattern H: Schema "Order" collides with requestBody "Order" which has + # 3 content types that all map to the "JSON" short name. + Order: + type: object + properties: + id: + type: string + product: + type: string + + Pet: + type: object + properties: + id: + type: integer + name: + type: string + + # Pattern I: schemas with x-go-type used as $ref targets in inline response properties. + # (oapi-codegen-exp#14) + Widget: + type: object + x-go-type: string + properties: + id: + type: string + + Metadata: + type: object + x-go-type: string + properties: + total: + type: integer + + # Pattern J: schema "Resource_MVO" collides with requestBody "Resource_MVO". + # The requestBody's content schemas $ref the component schema, plus one + # content type $refs a different schema (JsonPatch). The response for the + # PATCH operation has multiple JSON content types, two of which share an + # identical oneOf schema with inline members. + Resource_MVO: + type: object + properties: + name: + type: string + status: + type: string + + Resource: + type: object + properties: + id: + type: string + name: + type: string + status: + type: string + + JsonPatch: + type: array + items: + type: object + properties: + op: + type: string + path: + type: string + + # Pattern K: x-go-name on schema — should be pinned as "SpecialName" + Renamer: + x-go-name: SpecialName + type: object + properties: + label: + type: string + + # Pattern L: schema "Outcome" (no x-go-name — keeps bare name) + Outcome: + type: object + properties: + value: + type: string + + # Pattern M: schema "Payload" (no x-go-name — keeps bare name) + Payload: + type: object + properties: + content: + type: string + + # Pattern F: x-go-type-name extension + cross-section collision + # Schema "Qux" has x-go-type-name: CustomQux and collides with response "Qux". + Qux: + type: object + x-go-type-name: CustomQux + properties: + label: + type: string + + # Pattern G: x-go-type extension + cross-section collision + # Schema "Zap" has x-go-type: string and collides with response "Zap". + Zap: + type: object + x-go-type: string + properties: + unused: + type: string + + parameters: + Bar: + name: bar + in: query + schema: + type: string + + requestBodies: + Bar: + content: + application/json: + schema: + type: object + properties: + value: + type: integer + + # Pattern H: requestBody "Order" with 3 content types that all contain "json" + # and collapse to the same "JSON" suffix via contentTypeSuffix(). + Order: + content: + application/json: + schema: + type: object + properties: + id: + type: string + product: + type: string + application/merge-patch+json: + schema: + type: object + properties: + product: + type: string + application/json-patch+json: + schema: + type: array + items: + type: object + properties: + op: + type: string + path: + type: string + value: + type: string + + Pet: + content: + application/json: + schema: + type: object + properties: + name: + type: string + species: + type: string + + # Pattern M: requestBody "Payload" has x-go-name — should be pinned as "PayloadBody" + Payload: + x-go-name: PayloadBody + content: + application/json: + schema: + type: object + properties: + data: + type: string + + # Pattern J: requestBody "Resource_MVO" shares name with schema "Resource_MVO". + # Content schemas $ref the component schema, except json-patch which $refs JsonPatch. + Resource_MVO: + content: + application/json: + schema: + $ref: '#/components/schemas/Resource_MVO' + application/merge-patch+json: + schema: + $ref: '#/components/schemas/Resource_MVO' + application/json-patch+json: + schema: + $ref: '#/components/schemas/JsonPatch' + + headers: + Bar: + schema: + type: boolean + + responses: + Bar: + description: Bar response + headers: + X-Bar: + $ref: '#/components/headers/Bar' + content: + application/json: + schema: + type: object + properties: + value1: + $ref: '#/components/schemas/Bar' + value2: + $ref: '#/components/schemas/Bar2' + + # Pattern K: response "Renamer" — no x-go-name, should keep bare name "Renamer" + # because the schema collision is avoided by the schema's x-go-name: SpecialName. + Renamer: + description: A Renamer response + content: + application/json: + schema: + type: object + properties: + data: + type: string + + # Pattern L: response "Outcome" has x-go-name — should be pinned as "OutcomeResult" + Outcome: + x-go-name: OutcomeResult + description: An Outcome response + content: + application/json: + schema: + type: object + properties: + result: + type: string + + Qux: + description: A Qux response + content: + application/json: + schema: + type: object + properties: + data: + type: string + + Zap: + description: A Zap response + content: + application/json: + schema: + type: object + properties: + result: + type: string + + # Pattern J: response with multiple JSON content types where json-patch + # and json-patch-query variants share an identical oneOf schema with + # inline (non-$ref) members. The codegen must not emit duplicate type + # declarations for those inline members. + 200Resource_Patch: + description: Patch success + content: + application/json: + schema: + $ref: '#/components/schemas/Resource' + application/merge-patch+json: + schema: + $ref: '#/components/schemas/Resource' + application/json-patch+json: + schema: + oneOf: + - $ref: '#/components/schemas/Resource' + - type: array + items: + $ref: '#/components/schemas/Resource' + - type: string + nullable: true + application/json-patch-query+json: + schema: + oneOf: + - $ref: '#/components/schemas/Resource' + - type: array + items: + $ref: '#/components/schemas/Resource' + - type: string + nullable: true diff --git a/internal/test/outputoptions/disabletypealiases/config.yaml b/internal/test/outputoptions/disabletypealiases/config.yaml new file mode 100644 index 0000000000..b82daa271a --- /dev/null +++ b/internal/test/outputoptions/disabletypealiases/config.yaml @@ -0,0 +1,9 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: types +generate: + models: true +output: types.gen.go +output-options: + skip-prune: true + disable-type-aliases-for-type: + - array diff --git a/internal/test/outputoptions/disabletypealiases/generate.go b/internal/test/outputoptions/disabletypealiases/generate.go new file mode 100644 index 0000000000..8f0dd5b3e3 --- /dev/null +++ b/internal/test/outputoptions/disabletypealiases/generate.go @@ -0,0 +1,3 @@ +package types + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/outputoptions/disabletypealiases/spec.yaml b/internal/test/outputoptions/disabletypealiases/spec.yaml new file mode 100644 index 0000000000..e073eb3c64 --- /dev/null +++ b/internal/test/outputoptions/disabletypealiases/spec.yaml @@ -0,0 +1,13 @@ +components: + schemas: + my_item: + type: object + properties: + name: + type: string + age: + type: integer + example: + type: array + items: + $ref: '#/components/schemas/my_item' diff --git a/internal/test/outputoptions/disabletypealiases/types.gen.go b/internal/test/outputoptions/disabletypealiases/types.gen.go new file mode 100644 index 0000000000..f2537cfba5 --- /dev/null +++ b/internal/test/outputoptions/disabletypealiases/types.gen.go @@ -0,0 +1,13 @@ +// Package types provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package types + +// Example defines model for example. +type Example []MyItem + +// MyItem defines model for my_item. +type MyItem struct { + Age *int `json:"age,omitempty"` + Name *string `json:"name,omitempty"` +} diff --git a/internal/test/outputoptions/disabletypealiases/types_ext.go b/internal/test/outputoptions/disabletypealiases/types_ext.go new file mode 100644 index 0000000000..49103fd7f9 --- /dev/null +++ b/internal/test/outputoptions/disabletypealiases/types_ext.go @@ -0,0 +1,5 @@ +package types + +// MustCompile will only compile if the type it's defined on has type aliases disabled +func (*Example) MustCompile() { +} diff --git a/internal/test/outputoptions/name-normalizer/spec.yaml b/internal/test/outputoptions/name-normalizer/spec.yaml new file mode 100644 index 0000000000..524ecb70ad --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/spec.yaml @@ -0,0 +1,64 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Example code for the `name-normalizer` output option +paths: + /api/pets/{petId}: + get: + summary: Get pet given identifier. + operationId: getHttpPet + parameters: + - name: petId + in: path + required: true + schema: + type: string + responses: + '200': + description: valid pet + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' +components: + schemas: + Pet: + type: object + required: + - uuid + - name + properties: + uuid: + type: string + description: The pet uuid. + name: + type: string + description: The name of the pet. + Error: + required: + - code + - message + properties: + code: + type: integer + format: int32 + description: Error code + message: + type: string + description: Error message + OneOf2things: + description: "Notice that the `things` is not capitalised" + oneOf: + - type: object + required: + - id + properties: + id: + type: integer + - type: object + required: + - id + properties: + id: + type: string + format: uuid diff --git a/internal/test/outputoptions/name-normalizer/to-camel-case-with-additional-initialisms/config.yaml b/internal/test/outputoptions/name-normalizer/to-camel-case-with-additional-initialisms/config.yaml new file mode 100644 index 0000000000..1e0ffb5655 --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/to-camel-case-with-additional-initialisms/config.yaml @@ -0,0 +1,13 @@ +# yaml-language-server: $schema=../../../../../configuration-schema.json +package: tocamelcasewithadditionalinitialisms +generate: + gorilla-server: true + client: true + models: true + embedded-spec: true +output: name_normalizer.gen.go +output-options: + skip-prune: true + name-normalizer: ToCamelCaseWithInitialisms + additional-initialisms: + - NAME diff --git a/internal/test/outputoptions/name-normalizer/to-camel-case-with-additional-initialisms/generate.go b/internal/test/outputoptions/name-normalizer/to-camel-case-with-additional-initialisms/generate.go new file mode 100644 index 0000000000..c5611adbd0 --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/to-camel-case-with-additional-initialisms/generate.go @@ -0,0 +1,3 @@ +package tocamelcasewithadditionalinitialisms + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../spec.yaml diff --git a/internal/test/outputoptions/name-normalizer/to-camel-case-with-additional-initialisms/name_normalizer.gen.go b/internal/test/outputoptions/name-normalizer/to-camel-case-with-additional-initialisms/name_normalizer.gen.go new file mode 100644 index 0000000000..8d02762813 --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/to-camel-case-with-additional-initialisms/name_normalizer.gen.go @@ -0,0 +1,590 @@ +// Package tocamelcasewithadditionalinitialisms provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package tocamelcasewithadditionalinitialisms + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/gorilla/mux" + "github.com/oapi-codegen/runtime" + openapi_types "github.com/oapi-codegen/runtime/types" +) + +// Error defines model for Error. +type Error struct { + // Code Error code + Code int32 `json:"code"` + + // Message Error message + Message string `json:"message"` +} + +// OneOf2Things Notice that the `things` is not capitalised +type OneOf2Things struct { + union json.RawMessage +} + +// OneOf2Things0 defines model for . +type OneOf2Things0 struct { + ID int `json:"id"` +} + +// OneOf2Things1 defines model for . +type OneOf2Things1 struct { + ID openapi_types.UUID `json:"id"` +} + +// Pet defines model for Pet. +type Pet struct { + // NAME The name of the pet. + NAME string `json:"name"` + + // UUID The pet uuid. + UUID string `json:"uuid"` +} + +// AsOneOf2Things0 returns the union data inside the OneOf2Things as a OneOf2Things0 +func (t OneOf2Things) AsOneOf2Things0() (OneOf2Things0, error) { + var body OneOf2Things0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromOneOf2Things0 overwrites any union data inside the OneOf2Things as the provided OneOf2Things0 +func (t *OneOf2Things) FromOneOf2Things0(v OneOf2Things0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeOneOf2Things0 performs a merge with any union data inside the OneOf2Things, using the provided OneOf2Things0 +func (t *OneOf2Things) MergeOneOf2Things0(v OneOf2Things0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsOneOf2Things1 returns the union data inside the OneOf2Things as a OneOf2Things1 +func (t OneOf2Things) AsOneOf2Things1() (OneOf2Things1, error) { + var body OneOf2Things1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromOneOf2Things1 overwrites any union data inside the OneOf2Things as the provided OneOf2Things1 +func (t *OneOf2Things) FromOneOf2Things1(v OneOf2Things1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeOneOf2Things1 performs a merge with any union data inside the OneOf2Things, using the provided OneOf2Things1 +func (t *OneOf2Things) MergeOneOf2Things1(v OneOf2Things1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t OneOf2Things) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *OneOf2Things) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetHTTPPet request + GetHTTPPet(ctx context.Context, petID string, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) GetHTTPPet(ctx context.Context, petID string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetHTTPPetRequest(c.Server, petID) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetHTTPPetRequest generates requests for GetHTTPPet +func NewGetHTTPPetRequest(server string, petID string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "petId", petID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/pets/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetHTTPPetWithResponse request + GetHTTPPetWithResponse(ctx context.Context, petID string, reqEditors ...RequestEditorFn) (*GetHTTPPetResponse, error) +} + +type GetHTTPPetResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Pet +} + +// Status returns HTTPResponse.Status +func (r GetHTTPPetResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetHTTPPetResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// GetHTTPPetWithResponse request returning *GetHTTPPetResponse +func (c *ClientWithResponses) GetHTTPPetWithResponse(ctx context.Context, petID string, reqEditors ...RequestEditorFn) (*GetHTTPPetResponse, error) { + rsp, err := c.GetHTTPPet(ctx, petID, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetHTTPPetResponse(rsp) +} + +// ParseGetHTTPPetResponse parses an HTTP response from a GetHTTPPetWithResponse call +func ParseGetHTTPPetResponse(rsp *http.Response) (*GetHTTPPetResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetHTTPPetResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Pet + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Get pet given identifier. + // (GET /api/pets/{petId}) + GetHTTPPet(w http.ResponseWriter, r *http.Request, petID string) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetHTTPPet operation middleware +func (siw *ServerInterfaceWrapper) GetHTTPPet(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "petId" ------------- + var petID string + + err = runtime.BindStyledParameterWithOptions("simple", "petId", mux.Vars(r)["petId"], &petID, runtime.BindStyledParameterOptions{Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "petId", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetHTTPPet(w, r, petID) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{}) +} + +type GorillaServerOptions struct { + BaseURL string + BaseRouter *mux.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r *mux.Router) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r *mux.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options GorillaServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = mux.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.HandleFunc(options.BaseURL+"/api/pets/{petId}", wrapper.GetHTTPPet).Methods("GET") + + return r +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/4RTTW/bPAz+KwLf96jZWXrzvdh6WXvIrQgQzaJtFrakSXSwLvB/HyglWZZk6CWRRerR", + "8yEeoPVT8A4dJ2gOkNoBJ5OXjzH6KIsQfcDIhHm79Rbl32JqIwUm76ApzSrXNHQ+ToahAXL8sAYN/B6w", + "fGKPERYNE6Zk+n8Cncrno4kjuR6WRUPEHzNFtNC8wvHCU/t20fDs8Llb80CuT7fw3zxTi4oHw4oHVLvS", + "uFOUlPOsWhOIzUgJLWjwggXN67UHZOX3WtUVN7KwPfP339+wZVj0faizY/NM9kPVd5FF+wvybWDOTHd8", + "3gyopKJ8l40IyNXtxboQuns6ICupVh/yPYrKRG6JSze5zmdLiUepPf40UxgxPyjV+ViyEoBPTqwa6RfG", + "nfIzh5mVL7Q07DGmQvBztapWwt8HdCYQNPCQtzQEw0M2pjaB6oCc6kNAfrKLbPbFQjHQCOqThQa+IH/d", + "bF7EXjkfzYSMMeWXQXKdYJ4UNpDR4NIDjjPq43BdPJ2zX1tpTsG7VDJbr1Zl1hyjy4RMCCO1mVL9lkTj", + "4QLv/4gdNPBf/Wea6+Mo18I6m/x3hHszkpUQc1xpniYT34vWHG1Pe3SKLDqmjjBWArL8DgAA//+VcR3v", + "MAQAAA==", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/outputoptions/name-normalizer/to-camel-case-with-additional-initialisms/name_normalizer_test.go b/internal/test/outputoptions/name-normalizer/to-camel-case-with-additional-initialisms/name_normalizer_test.go new file mode 100644 index 0000000000..82d3b2d478 --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/to-camel-case-with-additional-initialisms/name_normalizer_test.go @@ -0,0 +1,24 @@ +package tocamelcasewithadditionalinitialisms + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGenCodeHasCorrectNames(t *testing.T) { + pet := &Pet{} + assert.Equal(t, "", pet.NAME) + assert.Equal(t, "", pet.UUID) + + uri := "https://my-api.com/some-base-url/v1/" + client, err := NewClient(uri) + assert.Nil(t, err) + assert.NotNil(t, client.GetHTTPPet) + + server := &ServerInterfaceWrapper{} + assert.NotNil(t, server.GetHTTPPet) + + oneOf := OneOf2Things{} + assert.Zero(t, oneOf) +} diff --git a/internal/test/outputoptions/name-normalizer/to-camel-case-with-digits/config.yaml b/internal/test/outputoptions/name-normalizer/to-camel-case-with-digits/config.yaml new file mode 100644 index 0000000000..59b69a9de3 --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/to-camel-case-with-digits/config.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../../../../../configuration-schema.json +package: tocamelcasewithdigits +generate: + gorilla-server: true + client: true + models: true + embedded-spec: true +output: name_normalizer.gen.go +output-options: + skip-prune: true + name-normalizer: ToCamelCaseWithDigits diff --git a/internal/test/outputoptions/name-normalizer/to-camel-case-with-digits/generate.go b/internal/test/outputoptions/name-normalizer/to-camel-case-with-digits/generate.go new file mode 100644 index 0000000000..4b4d2e44e6 --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/to-camel-case-with-digits/generate.go @@ -0,0 +1,3 @@ +package tocamelcasewithdigits + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../spec.yaml diff --git a/internal/test/outputoptions/name-normalizer/to-camel-case-with-digits/name_normalizer.gen.go b/internal/test/outputoptions/name-normalizer/to-camel-case-with-digits/name_normalizer.gen.go new file mode 100644 index 0000000000..206ddbf64d --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/to-camel-case-with-digits/name_normalizer.gen.go @@ -0,0 +1,590 @@ +// Package tocamelcasewithdigits provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package tocamelcasewithdigits + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/gorilla/mux" + "github.com/oapi-codegen/runtime" + openapi_types "github.com/oapi-codegen/runtime/types" +) + +// Error defines model for Error. +type Error struct { + // Code Error code + Code int32 `json:"code"` + + // Message Error message + Message string `json:"message"` +} + +// OneOf2Things Notice that the `things` is not capitalised +type OneOf2Things struct { + union json.RawMessage +} + +// OneOf2Things0 defines model for . +type OneOf2Things0 struct { + Id int `json:"id"` +} + +// OneOf2Things1 defines model for . +type OneOf2Things1 struct { + Id openapi_types.UUID `json:"id"` +} + +// Pet defines model for Pet. +type Pet struct { + // Name The name of the pet. + Name string `json:"name"` + + // Uuid The pet uuid. + Uuid string `json:"uuid"` +} + +// AsOneOf2Things0 returns the union data inside the OneOf2Things as a OneOf2Things0 +func (t OneOf2Things) AsOneOf2Things0() (OneOf2Things0, error) { + var body OneOf2Things0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromOneOf2Things0 overwrites any union data inside the OneOf2Things as the provided OneOf2Things0 +func (t *OneOf2Things) FromOneOf2Things0(v OneOf2Things0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeOneOf2Things0 performs a merge with any union data inside the OneOf2Things, using the provided OneOf2Things0 +func (t *OneOf2Things) MergeOneOf2Things0(v OneOf2Things0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsOneOf2Things1 returns the union data inside the OneOf2Things as a OneOf2Things1 +func (t OneOf2Things) AsOneOf2Things1() (OneOf2Things1, error) { + var body OneOf2Things1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromOneOf2Things1 overwrites any union data inside the OneOf2Things as the provided OneOf2Things1 +func (t *OneOf2Things) FromOneOf2Things1(v OneOf2Things1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeOneOf2Things1 performs a merge with any union data inside the OneOf2Things, using the provided OneOf2Things1 +func (t *OneOf2Things) MergeOneOf2Things1(v OneOf2Things1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t OneOf2Things) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *OneOf2Things) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetHttpPet request + GetHttpPet(ctx context.Context, petId string, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) GetHttpPet(ctx context.Context, petId string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetHttpPetRequest(c.Server, petId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetHttpPetRequest generates requests for GetHttpPet +func NewGetHttpPetRequest(server string, petId string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "petId", petId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/pets/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetHttpPetWithResponse request + GetHttpPetWithResponse(ctx context.Context, petId string, reqEditors ...RequestEditorFn) (*GetHttpPetResponse, error) +} + +type GetHttpPetResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Pet +} + +// Status returns HTTPResponse.Status +func (r GetHttpPetResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetHttpPetResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// GetHttpPetWithResponse request returning *GetHttpPetResponse +func (c *ClientWithResponses) GetHttpPetWithResponse(ctx context.Context, petId string, reqEditors ...RequestEditorFn) (*GetHttpPetResponse, error) { + rsp, err := c.GetHttpPet(ctx, petId, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetHttpPetResponse(rsp) +} + +// ParseGetHttpPetResponse parses an HTTP response from a GetHttpPetWithResponse call +func ParseGetHttpPetResponse(rsp *http.Response) (*GetHttpPetResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetHttpPetResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Pet + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Get pet given identifier. + // (GET /api/pets/{petId}) + GetHttpPet(w http.ResponseWriter, r *http.Request, petId string) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetHttpPet operation middleware +func (siw *ServerInterfaceWrapper) GetHttpPet(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "petId" ------------- + var petId string + + err = runtime.BindStyledParameterWithOptions("simple", "petId", mux.Vars(r)["petId"], &petId, runtime.BindStyledParameterOptions{Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "petId", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetHttpPet(w, r, petId) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{}) +} + +type GorillaServerOptions struct { + BaseURL string + BaseRouter *mux.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r *mux.Router) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r *mux.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options GorillaServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = mux.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.HandleFunc(options.BaseURL+"/api/pets/{petId}", wrapper.GetHttpPet).Methods("GET") + + return r +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/4RTzY7TMBB+FWvgaJLSveW+gr2wHLitKtXEk2RWiW3sScVS5d3R2G0pbVe9tI5n/Pn7", + "8eyh9VPwDh0naPaQ2gEnk5ePMfooixB9wMiEebv1FuXfYmojBSbvoCnNKtc0dD5OhqEBcvywBg38FrB8", + "Yo8RFg0TpmT6d4GO5dPRxJFcD8uiIeKvmSJaaF7gcOGxfbNoeHb43K15INena/hvnqlFxYNhxQOqbWnc", + "KkrKeVatCcRmpIQWNHjBgubl0gOy8nup6oIbWdic+Pufr9gyLPo21MmxeSZ7V/VNZNH+Hfk6MGemGz7/", + "GFBJRfkuGxGQq+uLdSF083RAVlKt7vI9iMpErolLN7nOZ0uJR6k9/jZTGDE/KNX5WLISgE9OrBrpD8at", + "8jOHmZUvtDTsMKZC8HO1qlbC3wd0JhA08JC3NATDQzamNoHqgJzqfUB+sots9sVCMdAI6pOFBr4gf2UO", + "Yq+cj2ZCxpjyyyC5TjCPChvIaHDuAccZ9WG4zp7Oya+NNKfgXSqZrVerMmuO0WVCJoSR2kypfk2icX+G", + "9zFiBw18qP9Nc30Y5VpYZ5P/j3BnRrISYo4rzdNk4lvRmqPtaYdOkUXH1BHGSkCWvwEAAP//D5F1qDAE", + "AAA=", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/outputoptions/name-normalizer/to-camel-case-with-digits/name_normalizer_test.go b/internal/test/outputoptions/name-normalizer/to-camel-case-with-digits/name_normalizer_test.go new file mode 100644 index 0000000000..32a01436e9 --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/to-camel-case-with-digits/name_normalizer_test.go @@ -0,0 +1,24 @@ +package tocamelcasewithdigits + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGenCodeHasCorrectNames(t *testing.T) { + pet := &Pet{} + assert.Equal(t, "", pet.Name) + assert.Equal(t, "", pet.Uuid) + + uri := "https://my-api.com/some-base-url/v1/" + client, err := NewClient(uri) + assert.Nil(t, err) + assert.NotNil(t, client.GetHttpPet) + + server := &ServerInterfaceWrapper{} + assert.NotNil(t, server.GetHttpPet) + + oneOf := OneOf2Things{} + assert.Zero(t, oneOf) +} diff --git a/internal/test/outputoptions/name-normalizer/to-camel-case-with-initialisms/config.yaml b/internal/test/outputoptions/name-normalizer/to-camel-case-with-initialisms/config.yaml new file mode 100644 index 0000000000..63e903aeaf --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/to-camel-case-with-initialisms/config.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../../../../../configuration-schema.json +package: tocamelcasewithinitialisms +generate: + gorilla-server: true + client: true + models: true + embedded-spec: true +output: name_normalizer.gen.go +output-options: + skip-prune: true + name-normalizer: ToCamelCaseWithInitialisms diff --git a/internal/test/outputoptions/name-normalizer/to-camel-case-with-initialisms/generate.go b/internal/test/outputoptions/name-normalizer/to-camel-case-with-initialisms/generate.go new file mode 100644 index 0000000000..5f65491318 --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/to-camel-case-with-initialisms/generate.go @@ -0,0 +1,3 @@ +package tocamelcasewithinitialisms + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../spec.yaml diff --git a/internal/test/outputoptions/name-normalizer/to-camel-case-with-initialisms/name_normalizer.gen.go b/internal/test/outputoptions/name-normalizer/to-camel-case-with-initialisms/name_normalizer.gen.go new file mode 100644 index 0000000000..6cf9d6e7fe --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/to-camel-case-with-initialisms/name_normalizer.gen.go @@ -0,0 +1,590 @@ +// Package tocamelcasewithinitialisms provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package tocamelcasewithinitialisms + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/gorilla/mux" + "github.com/oapi-codegen/runtime" + openapi_types "github.com/oapi-codegen/runtime/types" +) + +// Error defines model for Error. +type Error struct { + // Code Error code + Code int32 `json:"code"` + + // Message Error message + Message string `json:"message"` +} + +// OneOf2Things Notice that the `things` is not capitalised +type OneOf2Things struct { + union json.RawMessage +} + +// OneOf2Things0 defines model for . +type OneOf2Things0 struct { + ID int `json:"id"` +} + +// OneOf2Things1 defines model for . +type OneOf2Things1 struct { + ID openapi_types.UUID `json:"id"` +} + +// Pet defines model for Pet. +type Pet struct { + // Name The name of the pet. + Name string `json:"name"` + + // UUID The pet uuid. + UUID string `json:"uuid"` +} + +// AsOneOf2Things0 returns the union data inside the OneOf2Things as a OneOf2Things0 +func (t OneOf2Things) AsOneOf2Things0() (OneOf2Things0, error) { + var body OneOf2Things0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromOneOf2Things0 overwrites any union data inside the OneOf2Things as the provided OneOf2Things0 +func (t *OneOf2Things) FromOneOf2Things0(v OneOf2Things0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeOneOf2Things0 performs a merge with any union data inside the OneOf2Things, using the provided OneOf2Things0 +func (t *OneOf2Things) MergeOneOf2Things0(v OneOf2Things0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsOneOf2Things1 returns the union data inside the OneOf2Things as a OneOf2Things1 +func (t OneOf2Things) AsOneOf2Things1() (OneOf2Things1, error) { + var body OneOf2Things1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromOneOf2Things1 overwrites any union data inside the OneOf2Things as the provided OneOf2Things1 +func (t *OneOf2Things) FromOneOf2Things1(v OneOf2Things1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeOneOf2Things1 performs a merge with any union data inside the OneOf2Things, using the provided OneOf2Things1 +func (t *OneOf2Things) MergeOneOf2Things1(v OneOf2Things1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t OneOf2Things) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *OneOf2Things) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetHTTPPet request + GetHTTPPet(ctx context.Context, petID string, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) GetHTTPPet(ctx context.Context, petID string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetHTTPPetRequest(c.Server, petID) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetHTTPPetRequest generates requests for GetHTTPPet +func NewGetHTTPPetRequest(server string, petID string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "petId", petID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/pets/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetHTTPPetWithResponse request + GetHTTPPetWithResponse(ctx context.Context, petID string, reqEditors ...RequestEditorFn) (*GetHTTPPetResponse, error) +} + +type GetHTTPPetResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Pet +} + +// Status returns HTTPResponse.Status +func (r GetHTTPPetResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetHTTPPetResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// GetHTTPPetWithResponse request returning *GetHTTPPetResponse +func (c *ClientWithResponses) GetHTTPPetWithResponse(ctx context.Context, petID string, reqEditors ...RequestEditorFn) (*GetHTTPPetResponse, error) { + rsp, err := c.GetHTTPPet(ctx, petID, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetHTTPPetResponse(rsp) +} + +// ParseGetHTTPPetResponse parses an HTTP response from a GetHTTPPetWithResponse call +func ParseGetHTTPPetResponse(rsp *http.Response) (*GetHTTPPetResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetHTTPPetResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Pet + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Get pet given identifier. + // (GET /api/pets/{petId}) + GetHTTPPet(w http.ResponseWriter, r *http.Request, petID string) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetHTTPPet operation middleware +func (siw *ServerInterfaceWrapper) GetHTTPPet(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "petId" ------------- + var petID string + + err = runtime.BindStyledParameterWithOptions("simple", "petId", mux.Vars(r)["petId"], &petID, runtime.BindStyledParameterOptions{Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "petId", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetHTTPPet(w, r, petID) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{}) +} + +type GorillaServerOptions struct { + BaseURL string + BaseRouter *mux.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r *mux.Router) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r *mux.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options GorillaServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = mux.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.HandleFunc(options.BaseURL+"/api/pets/{petId}", wrapper.GetHTTPPet).Methods("GET") + + return r +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/4RTTW/bPAz+KwLf96jZWXrzvdh6WXvIrQgQzaJtFrakSXSwLvB/HyglWZZk6CWRRerR", + "8yEeoPVT8A4dJ2gOkNoBJ5OXjzH6KIsQfcDIhHm79Rbl32JqIwUm76ApzSrXNHQ+ToahAXL8sAYN/B6w", + "fGKPERYNE6Zk+n8Cncrno4kjuR6WRUPEHzNFtNC8wvHCU/t20fDs8Llb80CuT7fw3zxTi4oHw4oHVLvS", + "uFOUlPOsWhOIzUgJLWjwggXN67UHZOX3WtUVN7KwPfP339+wZVj0faizY/NM9kPVd5FF+wvybWDOTHd8", + "3gyopKJ8l40IyNXtxboQuns6ICupVh/yPYrKRG6JSze5zmdLiUepPf40UxgxPyjV+ViyEoBPTqwa6RfG", + "nfIzh5mVL7Q07DGmQvBztapWwt8HdCYQNPCQtzQEw0M2pjaB6oCc6kNAfrKLbPbFQjHQCOqThQa+IH/d", + "bF7EXjkfzYSMMeWXQXKdYJ4UNpDR4NIDjjPq43BdPJ2zX1tpTsG7VDJbr1Zl1hyjy4RMCCO1mVL9lkTj", + "4QLv/4gdNPBf/Wea6+Mo18I6m/x3hHszkpUQc1xpniYT34vWHG1Pe3SKLDqmjjBWArL8DgAA//+VcR3v", + "MAQAAA==", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/outputoptions/name-normalizer/to-camel-case-with-initialisms/name_normalizer_test.go b/internal/test/outputoptions/name-normalizer/to-camel-case-with-initialisms/name_normalizer_test.go new file mode 100644 index 0000000000..fea7b84dc5 --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/to-camel-case-with-initialisms/name_normalizer_test.go @@ -0,0 +1,24 @@ +package tocamelcasewithinitialisms + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGenCodeHasCorrectNames(t *testing.T) { + pet := &Pet{} + assert.Equal(t, "", pet.Name) + assert.Equal(t, "", pet.UUID) + + uri := "https://my-api.com/some-base-url/v1/" + client, err := NewClient(uri) + assert.Nil(t, err) + assert.NotNil(t, client.GetHTTPPet) + + server := &ServerInterfaceWrapper{} + assert.NotNil(t, server.GetHTTPPet) + + oneOf := OneOf2Things{} + assert.Zero(t, oneOf) +} diff --git a/internal/test/outputoptions/name-normalizer/to-camel-case/config.yaml b/internal/test/outputoptions/name-normalizer/to-camel-case/config.yaml new file mode 100644 index 0000000000..b80cdda8eb --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/to-camel-case/config.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../../../../../configuration-schema.json +package: tocamelcase +generate: + gorilla-server: true + client: true + models: true + embedded-spec: true +output: name_normalizer.gen.go +output-options: + skip-prune: true + name-normalizer: ToCamelCase diff --git a/internal/test/outputoptions/name-normalizer/to-camel-case/generate.go b/internal/test/outputoptions/name-normalizer/to-camel-case/generate.go new file mode 100644 index 0000000000..a6c179b79d --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/to-camel-case/generate.go @@ -0,0 +1,3 @@ +package tocamelcase + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../spec.yaml diff --git a/internal/test/outputoptions/name-normalizer/to-camel-case/name_normalizer.gen.go b/internal/test/outputoptions/name-normalizer/to-camel-case/name_normalizer.gen.go new file mode 100644 index 0000000000..e34d1c4663 --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/to-camel-case/name_normalizer.gen.go @@ -0,0 +1,590 @@ +// Package tocamelcase provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package tocamelcase + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/gorilla/mux" + "github.com/oapi-codegen/runtime" + openapi_types "github.com/oapi-codegen/runtime/types" +) + +// Error defines model for Error. +type Error struct { + // Code Error code + Code int32 `json:"code"` + + // Message Error message + Message string `json:"message"` +} + +// OneOf2things Notice that the `things` is not capitalised +type OneOf2things struct { + union json.RawMessage +} + +// OneOf2things0 defines model for . +type OneOf2things0 struct { + Id int `json:"id"` +} + +// OneOf2things1 defines model for . +type OneOf2things1 struct { + Id openapi_types.UUID `json:"id"` +} + +// Pet defines model for Pet. +type Pet struct { + // Name The name of the pet. + Name string `json:"name"` + + // Uuid The pet uuid. + Uuid string `json:"uuid"` +} + +// AsOneOf2things0 returns the union data inside the OneOf2things as a OneOf2things0 +func (t OneOf2things) AsOneOf2things0() (OneOf2things0, error) { + var body OneOf2things0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromOneOf2things0 overwrites any union data inside the OneOf2things as the provided OneOf2things0 +func (t *OneOf2things) FromOneOf2things0(v OneOf2things0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeOneOf2things0 performs a merge with any union data inside the OneOf2things, using the provided OneOf2things0 +func (t *OneOf2things) MergeOneOf2things0(v OneOf2things0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsOneOf2things1 returns the union data inside the OneOf2things as a OneOf2things1 +func (t OneOf2things) AsOneOf2things1() (OneOf2things1, error) { + var body OneOf2things1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromOneOf2things1 overwrites any union data inside the OneOf2things as the provided OneOf2things1 +func (t *OneOf2things) FromOneOf2things1(v OneOf2things1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeOneOf2things1 performs a merge with any union data inside the OneOf2things, using the provided OneOf2things1 +func (t *OneOf2things) MergeOneOf2things1(v OneOf2things1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t OneOf2things) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *OneOf2things) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetHttpPet request + GetHttpPet(ctx context.Context, petId string, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) GetHttpPet(ctx context.Context, petId string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetHttpPetRequest(c.Server, petId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetHttpPetRequest generates requests for GetHttpPet +func NewGetHttpPetRequest(server string, petId string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "petId", petId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/pets/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetHttpPetWithResponse request + GetHttpPetWithResponse(ctx context.Context, petId string, reqEditors ...RequestEditorFn) (*GetHttpPetResponse, error) +} + +type GetHttpPetResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Pet +} + +// Status returns HTTPResponse.Status +func (r GetHttpPetResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetHttpPetResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// GetHttpPetWithResponse request returning *GetHttpPetResponse +func (c *ClientWithResponses) GetHttpPetWithResponse(ctx context.Context, petId string, reqEditors ...RequestEditorFn) (*GetHttpPetResponse, error) { + rsp, err := c.GetHttpPet(ctx, petId, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetHttpPetResponse(rsp) +} + +// ParseGetHttpPetResponse parses an HTTP response from a GetHttpPetWithResponse call +func ParseGetHttpPetResponse(rsp *http.Response) (*GetHttpPetResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetHttpPetResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Pet + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Get pet given identifier. + // (GET /api/pets/{petId}) + GetHttpPet(w http.ResponseWriter, r *http.Request, petId string) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetHttpPet operation middleware +func (siw *ServerInterfaceWrapper) GetHttpPet(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "petId" ------------- + var petId string + + err = runtime.BindStyledParameterWithOptions("simple", "petId", mux.Vars(r)["petId"], &petId, runtime.BindStyledParameterOptions{Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "petId", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetHttpPet(w, r, petId) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{}) +} + +type GorillaServerOptions struct { + BaseURL string + BaseRouter *mux.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r *mux.Router) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r *mux.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options GorillaServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = mux.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.HandleFunc(options.BaseURL+"/api/pets/{petId}", wrapper.GetHttpPet).Methods("GET") + + return r +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/4RTzY7TMBB+FWvgaJLSveW+gr2wHLitKtXEk2RWiW3sScVS5d3R2G0pbVe9tI5n/Pn7", + "8eyh9VPwDh0naPaQ2gEnk5ePMfooixB9wMiEebv1FuXfYmojBSbvoCnNKtc0dD5OhqEBcvywBg38FrB8", + "Yo8RFg0TpmT6d4GO5dPRxJFcD8uiIeKvmSJaaF7gcOGxfbNoeHb43K15INena/hvnqlFxYNhxQOqbWnc", + "KkrKeVatCcRmpIQWNHjBgubl0gOy8nup6oIbWdic+Pufr9gyLPo21MmxeSZ7V/VNZNH+Hfk6MGemGz7/", + "GFBJRfkuGxGQq+uLdSF083RAVlKt7vI9iMpErolLN7nOZ0uJR6k9/jZTGDE/KNX5WLISgE9OrBrpD8at", + "8jOHmZUvtDTsMKZC8HO1qlbC3wd0JhA08JC3NATDQzamNoHqgJzqfUB+sots9sVCMdAI6pOFBr4gf2UO", + "Yq+cj2ZCxpjyyyC5TjCPChvIaHDuAccZ9WG4zp7Oya+NNKfgXSqZrVerMmuO0WVCJoSR2kypfk2icX+G", + "9zFiBw18qP9Nc30Y5VpYZ5P/j3BnRrISYo4rzdNk4lvRmqPtaYdOkUXH1BHGSkCWvwEAAP//D5F1qDAE", + "AAA=", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/outputoptions/name-normalizer/to-camel-case/name_normalizer_test.go b/internal/test/outputoptions/name-normalizer/to-camel-case/name_normalizer_test.go new file mode 100644 index 0000000000..3e0d83fa86 --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/to-camel-case/name_normalizer_test.go @@ -0,0 +1,24 @@ +package tocamelcase + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGenCodeHasCorrectNames(t *testing.T) { + pet := &Pet{} + assert.Equal(t, "", pet.Name) + assert.Equal(t, "", pet.Uuid) + + uri := "https://my-api.com/some-base-url/v1/" + client, err := NewClient(uri) + assert.Nil(t, err) + assert.NotNil(t, client.GetHttpPet) + + server := &ServerInterfaceWrapper{} + assert.NotNil(t, server.GetHttpPet) + + oneOf := OneOf2things{} + assert.Zero(t, oneOf) +} diff --git a/internal/test/outputoptions/name-normalizer/unset/config.yaml b/internal/test/outputoptions/name-normalizer/unset/config.yaml new file mode 100644 index 0000000000..bd2918ed51 --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/unset/config.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../../../../../configuration-schema.json +package: unset +generate: + gorilla-server: true + client: true + models: true + embedded-spec: true +output: name_normalizer.gen.go +output-options: + skip-prune: true + # When `name-normalizer` is unset diff --git a/internal/test/outputoptions/name-normalizer/unset/generate.go b/internal/test/outputoptions/name-normalizer/unset/generate.go new file mode 100644 index 0000000000..84fac60cba --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/unset/generate.go @@ -0,0 +1,3 @@ +package unset + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../spec.yaml diff --git a/internal/test/outputoptions/name-normalizer/unset/name_normalizer.gen.go b/internal/test/outputoptions/name-normalizer/unset/name_normalizer.gen.go new file mode 100644 index 0000000000..17f65ad9ee --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/unset/name_normalizer.gen.go @@ -0,0 +1,590 @@ +// Package unset provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package unset + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/gorilla/mux" + "github.com/oapi-codegen/runtime" + openapi_types "github.com/oapi-codegen/runtime/types" +) + +// Error defines model for Error. +type Error struct { + // Code Error code + Code int32 `json:"code"` + + // Message Error message + Message string `json:"message"` +} + +// OneOf2things Notice that the `things` is not capitalised +type OneOf2things struct { + union json.RawMessage +} + +// OneOf2things0 defines model for . +type OneOf2things0 struct { + Id int `json:"id"` +} + +// OneOf2things1 defines model for . +type OneOf2things1 struct { + Id openapi_types.UUID `json:"id"` +} + +// Pet defines model for Pet. +type Pet struct { + // Name The name of the pet. + Name string `json:"name"` + + // Uuid The pet uuid. + Uuid string `json:"uuid"` +} + +// AsOneOf2things0 returns the union data inside the OneOf2things as a OneOf2things0 +func (t OneOf2things) AsOneOf2things0() (OneOf2things0, error) { + var body OneOf2things0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromOneOf2things0 overwrites any union data inside the OneOf2things as the provided OneOf2things0 +func (t *OneOf2things) FromOneOf2things0(v OneOf2things0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeOneOf2things0 performs a merge with any union data inside the OneOf2things, using the provided OneOf2things0 +func (t *OneOf2things) MergeOneOf2things0(v OneOf2things0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsOneOf2things1 returns the union data inside the OneOf2things as a OneOf2things1 +func (t OneOf2things) AsOneOf2things1() (OneOf2things1, error) { + var body OneOf2things1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromOneOf2things1 overwrites any union data inside the OneOf2things as the provided OneOf2things1 +func (t *OneOf2things) FromOneOf2things1(v OneOf2things1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeOneOf2things1 performs a merge with any union data inside the OneOf2things, using the provided OneOf2things1 +func (t *OneOf2things) MergeOneOf2things1(v OneOf2things1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t OneOf2things) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *OneOf2things) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetHttpPet request + GetHttpPet(ctx context.Context, petId string, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) GetHttpPet(ctx context.Context, petId string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetHttpPetRequest(c.Server, petId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetHttpPetRequest generates requests for GetHttpPet +func NewGetHttpPetRequest(server string, petId string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "petId", petId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/pets/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetHttpPetWithResponse request + GetHttpPetWithResponse(ctx context.Context, petId string, reqEditors ...RequestEditorFn) (*GetHttpPetResponse, error) +} + +type GetHttpPetResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Pet +} + +// Status returns HTTPResponse.Status +func (r GetHttpPetResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetHttpPetResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// GetHttpPetWithResponse request returning *GetHttpPetResponse +func (c *ClientWithResponses) GetHttpPetWithResponse(ctx context.Context, petId string, reqEditors ...RequestEditorFn) (*GetHttpPetResponse, error) { + rsp, err := c.GetHttpPet(ctx, petId, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetHttpPetResponse(rsp) +} + +// ParseGetHttpPetResponse parses an HTTP response from a GetHttpPetWithResponse call +func ParseGetHttpPetResponse(rsp *http.Response) (*GetHttpPetResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetHttpPetResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Pet + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Get pet given identifier. + // (GET /api/pets/{petId}) + GetHttpPet(w http.ResponseWriter, r *http.Request, petId string) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetHttpPet operation middleware +func (siw *ServerInterfaceWrapper) GetHttpPet(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "petId" ------------- + var petId string + + err = runtime.BindStyledParameterWithOptions("simple", "petId", mux.Vars(r)["petId"], &petId, runtime.BindStyledParameterOptions{Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "petId", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetHttpPet(w, r, petId) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{}) +} + +type GorillaServerOptions struct { + BaseURL string + BaseRouter *mux.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r *mux.Router) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r *mux.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options GorillaServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = mux.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.HandleFunc(options.BaseURL+"/api/pets/{petId}", wrapper.GetHttpPet).Methods("GET") + + return r +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/4RTzY7TMBB+FWvgaJLSveW+gr2wHLitKtXEk2RWiW3sScVS5d3R2G0pbVe9tI5n/Pn7", + "8eyh9VPwDh0naPaQ2gEnk5ePMfooixB9wMiEebv1FuXfYmojBSbvoCnNKtc0dD5OhqEBcvywBg38FrB8", + "Yo8RFg0TpmT6d4GO5dPRxJFcD8uiIeKvmSJaaF7gcOGxfbNoeHb43K15INena/hvnqlFxYNhxQOqbWnc", + "KkrKeVatCcRmpIQWNHjBgubl0gOy8nup6oIbWdic+Pufr9gyLPo21MmxeSZ7V/VNZNH+Hfk6MGemGz7/", + "GFBJRfkuGxGQq+uLdSF083RAVlKt7vI9iMpErolLN7nOZ0uJR6k9/jZTGDE/KNX5WLISgE9OrBrpD8at", + "8jOHmZUvtDTsMKZC8HO1qlbC3wd0JhA08JC3NATDQzamNoHqgJzqfUB+sots9sVCMdAI6pOFBr4gf2UO", + "Yq+cj2ZCxpjyyyC5TjCPChvIaHDuAccZ9WG4zp7Oya+NNKfgXSqZrVerMmuO0WVCJoSR2kypfk2icX+G", + "9zFiBw18qP9Nc30Y5VpYZ5P/j3BnRrISYo4rzdNk4lvRmqPtaYdOkUXH1BHGSkCWvwEAAP//D5F1qDAE", + "AAA=", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/outputoptions/name-normalizer/unset/name_normalizer_test.go b/internal/test/outputoptions/name-normalizer/unset/name_normalizer_test.go new file mode 100644 index 0000000000..9702a59c40 --- /dev/null +++ b/internal/test/outputoptions/name-normalizer/unset/name_normalizer_test.go @@ -0,0 +1,24 @@ +package unset + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGenCodeHasCorrectNames(t *testing.T) { + pet := &Pet{} + assert.Equal(t, "", pet.Name) + assert.Equal(t, "", pet.Uuid) + + uri := "https://my-api.com/some-base-url/v1/" + client, err := NewClient(uri) + assert.Nil(t, err) + assert.NotNil(t, client.GetHttpPet) + + server := &ServerInterfaceWrapper{} + assert.NotNil(t, server.GetHttpPet) + + oneOf := OneOf2things{} + assert.Zero(t, oneOf) +} diff --git a/internal/test/outputoptions/yaml-tags/config.yaml b/internal/test/outputoptions/yaml-tags/config.yaml new file mode 100644 index 0000000000..6f1e3eef12 --- /dev/null +++ b/internal/test/outputoptions/yaml-tags/config.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: yamltags +generate: + models: true +output-options: + skip-prune: true + yaml-tags: true +output: yamltags.go diff --git a/internal/test/outputoptions/yaml-tags/generate.go b/internal/test/outputoptions/yaml-tags/generate.go new file mode 100644 index 0000000000..1da2444608 --- /dev/null +++ b/internal/test/outputoptions/yaml-tags/generate.go @@ -0,0 +1,3 @@ +package yamltags + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/outputoptions/yaml-tags/spec.yaml b/internal/test/outputoptions/yaml-tags/spec.yaml new file mode 100644 index 0000000000..f30596224f --- /dev/null +++ b/internal/test/outputoptions/yaml-tags/spec.yaml @@ -0,0 +1,24 @@ +openapi: "3.0.1" +info: + version: 1.0.0 + title: Cookie parameters +paths: + /cookies: + get: + operationId: cookieParams + parameters: + - name: authId + description: Cookie parameter + in: cookie + required: false + schema: + type: string + - name: serverId + description: Another cookie parameter + in: cookie + required: false + schema: + type: string + responses: + 204: + description: no content diff --git a/internal/test/outputoptions/yaml-tags/yamltags.go b/internal/test/outputoptions/yaml-tags/yamltags.go new file mode 100644 index 0000000000..6422e7391e --- /dev/null +++ b/internal/test/outputoptions/yaml-tags/yamltags.go @@ -0,0 +1,13 @@ +// Package yamltags provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package yamltags + +// CookieParamsParams defines parameters for CookieParams. +type CookieParamsParams struct { + // AuthId Cookie parameter + AuthId *string `form:"authId,omitempty" json:"authId,omitempty" yaml:"authId,omitempty"` + + // ServerId Another cookie parameter + ServerId *string `form:"serverId,omitempty" json:"serverId,omitempty" yaml:"serverId,omitempty"` +} diff --git a/internal/test/parameters/config.yaml b/internal/test/parameters/config.yaml index 962aa2c573..64850f5de5 100644 --- a/internal/test/parameters/config.yaml +++ b/internal/test/parameters/config.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../configuration-schema.json package: parameters generate: echo-server: true diff --git a/internal/test/parameters/doc.go b/internal/test/parameters/doc.go index 7868d53fff..30d5b502e5 100644 --- a/internal/test/parameters/doc.go +++ b/internal/test/parameters/doc.go @@ -1,3 +1,3 @@ package parameters -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml parameters.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml parameters.yaml diff --git a/internal/test/parameters/parameters.gen.go b/internal/test/parameters/parameters.gen.go index 45d11b462c..5821715fc2 100644 --- a/internal/test/parameters/parameters.gen.go +++ b/internal/test/parameters/parameters.gen.go @@ -1,6 +1,6 @@ // Package parameters provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package parameters import ( @@ -16,9 +16,9 @@ import ( "path" "strings" - "github.com/deepmap/oapi-codegen/pkg/runtime" "github.com/getkin/kin-openapi/openapi3" "github.com/labstack/echo/v4" + "github.com/oapi-codegen/runtime" ) // Defines values for EnumParamsParamsEnumPathParam. @@ -27,6 +27,18 @@ const ( N200 EnumParamsParamsEnumPathParam = 200 ) +// Valid indicates whether the value is a known member of the EnumParamsParamsEnumPathParam enum. +func (e EnumParamsParamsEnumPathParam) Valid() bool { + switch e { + case N100: + return true + case N200: + return true + default: + return false + } +} + // ComplexObject defines model for ComplexObject. type ComplexObject struct { Id int `json:"Id"` @@ -588,128 +600,130 @@ func NewGetCookieRequest(server string, params *GetCookieParams) (*http.Request, return nil, err } - if params.P != nil { - var cookieParam0 string + if params != nil { - cookieParam0, err = runtime.StyleParamWithLocation("simple", false, "p", runtime.ParamLocationCookie, *params.P) - if err != nil { - return nil, err - } + if params.P != nil { + var cookieParam0 string + + cookieParam0, err = runtime.StyleParamWithOptions("simple", false, "p", *params.P, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationCookie, Type: "integer", Format: "int32"}) + if err != nil { + return nil, err + } - cookie0 := &http.Cookie{ - Name: "p", - Value: cookieParam0, + cookie0 := &http.Cookie{ + Name: "p", + Value: cookieParam0, + } + req.AddCookie(cookie0) } - req.AddCookie(cookie0) - } - if params.Ep != nil { - var cookieParam1 string + if params.Ep != nil { + var cookieParam1 string - cookieParam1, err = runtime.StyleParamWithLocation("simple", true, "ep", runtime.ParamLocationCookie, *params.Ep) - if err != nil { - return nil, err - } + cookieParam1, err = runtime.StyleParamWithOptions("simple", true, "ep", *params.Ep, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationCookie, Type: "integer", Format: "int32"}) + if err != nil { + return nil, err + } - cookie1 := &http.Cookie{ - Name: "ep", - Value: cookieParam1, + cookie1 := &http.Cookie{ + Name: "ep", + Value: cookieParam1, + } + req.AddCookie(cookie1) } - req.AddCookie(cookie1) - } - if params.Ea != nil { - var cookieParam2 string + if params.Ea != nil { + var cookieParam2 string - cookieParam2, err = runtime.StyleParamWithLocation("simple", true, "ea", runtime.ParamLocationCookie, *params.Ea) - if err != nil { - return nil, err - } + cookieParam2, err = runtime.StyleParamWithOptions("simple", true, "ea", *params.Ea, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationCookie, Type: "array", Format: ""}) + if err != nil { + return nil, err + } - cookie2 := &http.Cookie{ - Name: "ea", - Value: cookieParam2, + cookie2 := &http.Cookie{ + Name: "ea", + Value: cookieParam2, + } + req.AddCookie(cookie2) } - req.AddCookie(cookie2) - } - if params.A != nil { - var cookieParam3 string + if params.A != nil { + var cookieParam3 string - cookieParam3, err = runtime.StyleParamWithLocation("simple", false, "a", runtime.ParamLocationCookie, *params.A) - if err != nil { - return nil, err - } + cookieParam3, err = runtime.StyleParamWithOptions("simple", false, "a", *params.A, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationCookie, Type: "array", Format: ""}) + if err != nil { + return nil, err + } - cookie3 := &http.Cookie{ - Name: "a", - Value: cookieParam3, + cookie3 := &http.Cookie{ + Name: "a", + Value: cookieParam3, + } + req.AddCookie(cookie3) } - req.AddCookie(cookie3) - } - if params.Eo != nil { - var cookieParam4 string + if params.Eo != nil { + var cookieParam4 string - cookieParam4, err = runtime.StyleParamWithLocation("simple", true, "eo", runtime.ParamLocationCookie, *params.Eo) - if err != nil { - return nil, err - } + cookieParam4, err = runtime.StyleParamWithOptions("simple", true, "eo", *params.Eo, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationCookie, Type: "", Format: ""}) + if err != nil { + return nil, err + } - cookie4 := &http.Cookie{ - Name: "eo", - Value: cookieParam4, + cookie4 := &http.Cookie{ + Name: "eo", + Value: cookieParam4, + } + req.AddCookie(cookie4) } - req.AddCookie(cookie4) - } - if params.O != nil { - var cookieParam5 string + if params.O != nil { + var cookieParam5 string - cookieParam5, err = runtime.StyleParamWithLocation("simple", false, "o", runtime.ParamLocationCookie, *params.O) - if err != nil { - return nil, err - } + cookieParam5, err = runtime.StyleParamWithOptions("simple", false, "o", *params.O, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationCookie, Type: "", Format: ""}) + if err != nil { + return nil, err + } - cookie5 := &http.Cookie{ - Name: "o", - Value: cookieParam5, + cookie5 := &http.Cookie{ + Name: "o", + Value: cookieParam5, + } + req.AddCookie(cookie5) } - req.AddCookie(cookie5) - } - if params.Co != nil { - var cookieParam6 string + if params.Co != nil { + var cookieParam6 string - var cookieParamBuf6 []byte - cookieParamBuf6, err = json.Marshal(*params.Co) - if err != nil { - return nil, err - } - cookieParam6 = url.QueryEscape(string(cookieParamBuf6)) + var cookieParamBuf6 []byte + cookieParamBuf6, err = json.Marshal(*params.Co) + if err != nil { + return nil, err + } + cookieParam6 = url.QueryEscape(string(cookieParamBuf6)) - cookie6 := &http.Cookie{ - Name: "co", - Value: cookieParam6, + cookie6 := &http.Cookie{ + Name: "co", + Value: cookieParam6, + } + req.AddCookie(cookie6) } - req.AddCookie(cookie6) - } - if params.N1s != nil { - var cookieParam7 string + if params.N1s != nil { + var cookieParam7 string - cookieParam7, err = runtime.StyleParamWithLocation("simple", true, "1s", runtime.ParamLocationCookie, *params.N1s) - if err != nil { - return nil, err - } + cookieParam7, err = runtime.StyleParamWithOptions("simple", true, "1s", *params.N1s, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationCookie, Type: "string", Format: ""}) + if err != nil { + return nil, err + } - cookie7 := &http.Cookie{ - Name: "1s", - Value: cookieParam7, + cookie7 := &http.Cookie{ + Name: "1s", + Value: cookieParam7, + } + req.AddCookie(cookie7) } - req.AddCookie(cookie7) } - return req, nil } @@ -732,26 +746,28 @@ func NewEnumParamsRequest(server string, params *EnumParamsParams) (*http.Reques return nil, err } - queryValues := queryURL.Query() + if params != nil { + queryValues := queryURL.Query() - if params.EnumPathParam != nil { + if params.EnumPathParam != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "enumPathParam", runtime.ParamLocationQuery, *params.EnumPathParam); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "enumPathParam", *params.EnumPathParam, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "integer", Format: "int32"}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } + } + queryURL.RawQuery = queryValues.Encode() } - queryURL.RawQuery = queryValues.Encode() - req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -784,94 +800,98 @@ func NewGetHeaderRequest(server string, params *GetHeaderParams) (*http.Request, return nil, err } - if params.XPrimitive != nil { - var headerParam0 string + if params != nil { - headerParam0, err = runtime.StyleParamWithLocation("simple", false, "X-Primitive", runtime.ParamLocationHeader, *params.XPrimitive) - if err != nil { - return nil, err + if params.XPrimitive != nil { + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "X-Primitive", *params.XPrimitive, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "integer", Format: "int32"}) + if err != nil { + return nil, err + } + + req.Header.Set("X-Primitive", headerParam0) } - req.Header.Set("X-Primitive", headerParam0) - } + if params.XPrimitiveExploded != nil { + var headerParam1 string - if params.XPrimitiveExploded != nil { - var headerParam1 string + headerParam1, err = runtime.StyleParamWithOptions("simple", true, "X-Primitive-Exploded", *params.XPrimitiveExploded, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "integer", Format: "int32"}) + if err != nil { + return nil, err + } - headerParam1, err = runtime.StyleParamWithLocation("simple", true, "X-Primitive-Exploded", runtime.ParamLocationHeader, *params.XPrimitiveExploded) - if err != nil { - return nil, err + req.Header.Set("X-Primitive-Exploded", headerParam1) } - req.Header.Set("X-Primitive-Exploded", headerParam1) - } + if params.XArrayExploded != nil { + var headerParam2 string - if params.XArrayExploded != nil { - var headerParam2 string + headerParam2, err = runtime.StyleParamWithOptions("simple", true, "X-Array-Exploded", *params.XArrayExploded, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "array", Format: ""}) + if err != nil { + return nil, err + } - headerParam2, err = runtime.StyleParamWithLocation("simple", true, "X-Array-Exploded", runtime.ParamLocationHeader, *params.XArrayExploded) - if err != nil { - return nil, err + req.Header.Set("X-Array-Exploded", headerParam2) } - req.Header.Set("X-Array-Exploded", headerParam2) - } + if params.XArray != nil { + var headerParam3 string - if params.XArray != nil { - var headerParam3 string + headerParam3, err = runtime.StyleParamWithOptions("simple", false, "X-Array", *params.XArray, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "array", Format: ""}) + if err != nil { + return nil, err + } - headerParam3, err = runtime.StyleParamWithLocation("simple", false, "X-Array", runtime.ParamLocationHeader, *params.XArray) - if err != nil { - return nil, err + req.Header.Set("X-Array", headerParam3) } - req.Header.Set("X-Array", headerParam3) - } + if params.XObjectExploded != nil { + var headerParam4 string - if params.XObjectExploded != nil { - var headerParam4 string + headerParam4, err = runtime.StyleParamWithOptions("simple", true, "X-Object-Exploded", *params.XObjectExploded, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "", Format: ""}) + if err != nil { + return nil, err + } - headerParam4, err = runtime.StyleParamWithLocation("simple", true, "X-Object-Exploded", runtime.ParamLocationHeader, *params.XObjectExploded) - if err != nil { - return nil, err + req.Header.Set("X-Object-Exploded", headerParam4) } - req.Header.Set("X-Object-Exploded", headerParam4) - } + if params.XObject != nil { + var headerParam5 string - if params.XObject != nil { - var headerParam5 string + headerParam5, err = runtime.StyleParamWithOptions("simple", false, "X-Object", *params.XObject, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "", Format: ""}) + if err != nil { + return nil, err + } - headerParam5, err = runtime.StyleParamWithLocation("simple", false, "X-Object", runtime.ParamLocationHeader, *params.XObject) - if err != nil { - return nil, err + req.Header.Set("X-Object", headerParam5) } - req.Header.Set("X-Object", headerParam5) - } + if params.XComplexObject != nil { + var headerParam6 string - if params.XComplexObject != nil { - var headerParam6 string + var headerParamBuf6 []byte + headerParamBuf6, err = json.Marshal(*params.XComplexObject) + if err != nil { + return nil, err + } + headerParam6 = string(headerParamBuf6) - var headerParamBuf6 []byte - headerParamBuf6, err = json.Marshal(*params.XComplexObject) - if err != nil { - return nil, err + req.Header.Set("X-Complex-Object", headerParam6) } - headerParam6 = string(headerParamBuf6) - req.Header.Set("X-Complex-Object", headerParam6) - } + if params.N1StartingWithNumber != nil { + var headerParam7 string - if params.N1StartingWithNumber != nil { - var headerParam7 string + headerParam7, err = runtime.StyleParamWithOptions("simple", false, "1-Starting-With-Number", *params.N1StartingWithNumber, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } - headerParam7, err = runtime.StyleParamWithLocation("simple", false, "1-Starting-With-Number", runtime.ParamLocationHeader, *params.N1StartingWithNumber) - if err != nil { - return nil, err + req.Header.Set("1-Starting-With-Number", headerParam7) } - req.Header.Set("1-Starting-With-Number", headerParam7) } return req, nil @@ -883,7 +903,7 @@ func NewGetLabelExplodeArrayRequest(server string, param []int32) (*http.Request var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("label", true, "param", runtime.ParamLocationPath, param) + pathParam0, err = runtime.StyleParamWithOptions("label", true, "param", param, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "array", Format: ""}) if err != nil { return nil, err } @@ -917,7 +937,7 @@ func NewGetLabelExplodeObjectRequest(server string, param Object) (*http.Request var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("label", true, "param", runtime.ParamLocationPath, param) + pathParam0, err = runtime.StyleParamWithOptions("label", true, "param", param, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "", Format: ""}) if err != nil { return nil, err } @@ -951,7 +971,7 @@ func NewGetLabelNoExplodeArrayRequest(server string, param []int32) (*http.Reque var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("label", false, "param", runtime.ParamLocationPath, param) + pathParam0, err = runtime.StyleParamWithOptions("label", false, "param", param, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "array", Format: ""}) if err != nil { return nil, err } @@ -985,7 +1005,7 @@ func NewGetLabelNoExplodeObjectRequest(server string, param Object) (*http.Reque var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("label", false, "param", runtime.ParamLocationPath, param) + pathParam0, err = runtime.StyleParamWithOptions("label", false, "param", param, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "", Format: ""}) if err != nil { return nil, err } @@ -1019,7 +1039,7 @@ func NewGetMatrixExplodeArrayRequest(server string, id []int32) (*http.Request, var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("matrix", true, "id", runtime.ParamLocationPath, id) + pathParam0, err = runtime.StyleParamWithOptions("matrix", true, "id", id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "array", Format: ""}) if err != nil { return nil, err } @@ -1053,7 +1073,7 @@ func NewGetMatrixExplodeObjectRequest(server string, id Object) (*http.Request, var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("matrix", true, "id", runtime.ParamLocationPath, id) + pathParam0, err = runtime.StyleParamWithOptions("matrix", true, "id", id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "", Format: ""}) if err != nil { return nil, err } @@ -1087,7 +1107,7 @@ func NewGetMatrixNoExplodeArrayRequest(server string, id []int32) (*http.Request var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("matrix", false, "id", runtime.ParamLocationPath, id) + pathParam0, err = runtime.StyleParamWithOptions("matrix", false, "id", id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "array", Format: ""}) if err != nil { return nil, err } @@ -1121,7 +1141,7 @@ func NewGetMatrixNoExplodeObjectRequest(server string, id Object) (*http.Request var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("matrix", false, "id", runtime.ParamLocationPath, id) + pathParam0, err = runtime.StyleParamWithOptions("matrix", false, "id", id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "", Format: ""}) if err != nil { return nil, err } @@ -1199,21 +1219,23 @@ func NewGetDeepObjectRequest(server string, params *GetDeepObjectParams) (*http. return nil, err } - queryValues := queryURL.Query() + if params != nil { + queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("deepObject", true, "deepObj", runtime.ParamLocationQuery, params.DeepObj); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if queryFrag, err := runtime.StyleParamWithOptions("deepObject", true, "deepObj", params.DeepObj, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "", Format: ""}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } - } - queryURL.RawQuery = queryValues.Encode() + queryURL.RawQuery = queryValues.Encode() + } req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { @@ -1242,148 +1264,150 @@ func NewGetQueryFormRequest(server string, params *GetQueryFormParams) (*http.Re return nil, err } - queryValues := queryURL.Query() + if params != nil { + queryValues := queryURL.Query() - if params.Ea != nil { + if params.Ea != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "ea", runtime.ParamLocationQuery, *params.Ea); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "ea", *params.Ea, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "array", Format: ""}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } - } - } + } - if params.A != nil { + if params.A != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", false, "a", runtime.ParamLocationQuery, *params.A); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if queryFrag, err := runtime.StyleParamWithOptions("form", false, "a", *params.A, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "array", Format: ""}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } - } - } + } - if params.Eo != nil { + if params.Eo != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "eo", runtime.ParamLocationQuery, *params.Eo); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "eo", *params.Eo, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "", Format: ""}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } - } - } + } - if params.O != nil { + if params.O != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", false, "o", runtime.ParamLocationQuery, *params.O); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if queryFrag, err := runtime.StyleParamWithOptions("form", false, "o", *params.O, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "", Format: ""}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } - } - } + } - if params.Ep != nil { + if params.Ep != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "ep", runtime.ParamLocationQuery, *params.Ep); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "ep", *params.Ep, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "integer", Format: "int32"}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } - } - } + } - if params.P != nil { + if params.P != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", false, "p", runtime.ParamLocationQuery, *params.P); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if queryFrag, err := runtime.StyleParamWithOptions("form", false, "p", *params.P, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "integer", Format: "int32"}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } - } - } + } - if params.Ps != nil { + if params.Ps != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "ps", runtime.ParamLocationQuery, *params.Ps); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "ps", *params.Ps, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } + } - } + if params.Co != nil { - if params.Co != nil { + if queryParamBuf, err := json.Marshal(*params.Co); err != nil { + return nil, err + } else { + queryValues.Add("co", string(queryParamBuf)) + } - if queryParamBuf, err := json.Marshal(*params.Co); err != nil { - return nil, err - } else { - queryValues.Add("co", string(queryParamBuf)) } - } - - if params.N1s != nil { + if params.N1s != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "1s", runtime.ParamLocationQuery, *params.N1s); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "1s", *params.N1s, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } + } + queryURL.RawQuery = queryValues.Encode() } - queryURL.RawQuery = queryValues.Encode() - req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -1398,7 +1422,7 @@ func NewGetSimpleExplodeArrayRequest(server string, param []int32) (*http.Reques var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", true, "param", runtime.ParamLocationPath, param) + pathParam0, err = runtime.StyleParamWithOptions("simple", true, "param", param, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "array", Format: ""}) if err != nil { return nil, err } @@ -1432,7 +1456,7 @@ func NewGetSimpleExplodeObjectRequest(server string, param Object) (*http.Reques var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", true, "param", runtime.ParamLocationPath, param) + pathParam0, err = runtime.StyleParamWithOptions("simple", true, "param", param, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "", Format: ""}) if err != nil { return nil, err } @@ -1466,7 +1490,7 @@ func NewGetSimpleNoExplodeArrayRequest(server string, param []int32) (*http.Requ var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "param", runtime.ParamLocationPath, param) + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "param", param, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "array", Format: ""}) if err != nil { return nil, err } @@ -1500,7 +1524,7 @@ func NewGetSimpleNoExplodeObjectRequest(server string, param Object) (*http.Requ var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "param", runtime.ParamLocationPath, param) + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "param", param, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "", Format: ""}) if err != nil { return nil, err } @@ -1534,7 +1558,7 @@ func NewGetSimplePrimitiveRequest(server string, param int32) (*http.Request, er var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "param", runtime.ParamLocationPath, param) + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "param", param, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "integer", Format: "int32"}) if err != nil { return nil, err } @@ -1636,67 +1660,67 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { - // GetContentObject request + // GetContentObjectWithResponse request GetContentObjectWithResponse(ctx context.Context, param ComplexObject, reqEditors ...RequestEditorFn) (*GetContentObjectResponse, error) - // GetCookie request + // GetCookieWithResponse request GetCookieWithResponse(ctx context.Context, params *GetCookieParams, reqEditors ...RequestEditorFn) (*GetCookieResponse, error) - // EnumParams request + // EnumParamsWithResponse request EnumParamsWithResponse(ctx context.Context, params *EnumParamsParams, reqEditors ...RequestEditorFn) (*EnumParamsResponse, error) - // GetHeader request + // GetHeaderWithResponse request GetHeaderWithResponse(ctx context.Context, params *GetHeaderParams, reqEditors ...RequestEditorFn) (*GetHeaderResponse, error) - // GetLabelExplodeArray request + // GetLabelExplodeArrayWithResponse request GetLabelExplodeArrayWithResponse(ctx context.Context, param []int32, reqEditors ...RequestEditorFn) (*GetLabelExplodeArrayResponse, error) - // GetLabelExplodeObject request + // GetLabelExplodeObjectWithResponse request GetLabelExplodeObjectWithResponse(ctx context.Context, param Object, reqEditors ...RequestEditorFn) (*GetLabelExplodeObjectResponse, error) - // GetLabelNoExplodeArray request + // GetLabelNoExplodeArrayWithResponse request GetLabelNoExplodeArrayWithResponse(ctx context.Context, param []int32, reqEditors ...RequestEditorFn) (*GetLabelNoExplodeArrayResponse, error) - // GetLabelNoExplodeObject request + // GetLabelNoExplodeObjectWithResponse request GetLabelNoExplodeObjectWithResponse(ctx context.Context, param Object, reqEditors ...RequestEditorFn) (*GetLabelNoExplodeObjectResponse, error) - // GetMatrixExplodeArray request + // GetMatrixExplodeArrayWithResponse request GetMatrixExplodeArrayWithResponse(ctx context.Context, id []int32, reqEditors ...RequestEditorFn) (*GetMatrixExplodeArrayResponse, error) - // GetMatrixExplodeObject request + // GetMatrixExplodeObjectWithResponse request GetMatrixExplodeObjectWithResponse(ctx context.Context, id Object, reqEditors ...RequestEditorFn) (*GetMatrixExplodeObjectResponse, error) - // GetMatrixNoExplodeArray request + // GetMatrixNoExplodeArrayWithResponse request GetMatrixNoExplodeArrayWithResponse(ctx context.Context, id []int32, reqEditors ...RequestEditorFn) (*GetMatrixNoExplodeArrayResponse, error) - // GetMatrixNoExplodeObject request + // GetMatrixNoExplodeObjectWithResponse request GetMatrixNoExplodeObjectWithResponse(ctx context.Context, id Object, reqEditors ...RequestEditorFn) (*GetMatrixNoExplodeObjectResponse, error) - // GetPassThrough request + // GetPassThroughWithResponse request GetPassThroughWithResponse(ctx context.Context, param string, reqEditors ...RequestEditorFn) (*GetPassThroughResponse, error) - // GetDeepObject request + // GetDeepObjectWithResponse request GetDeepObjectWithResponse(ctx context.Context, params *GetDeepObjectParams, reqEditors ...RequestEditorFn) (*GetDeepObjectResponse, error) - // GetQueryForm request + // GetQueryFormWithResponse request GetQueryFormWithResponse(ctx context.Context, params *GetQueryFormParams, reqEditors ...RequestEditorFn) (*GetQueryFormResponse, error) - // GetSimpleExplodeArray request + // GetSimpleExplodeArrayWithResponse request GetSimpleExplodeArrayWithResponse(ctx context.Context, param []int32, reqEditors ...RequestEditorFn) (*GetSimpleExplodeArrayResponse, error) - // GetSimpleExplodeObject request + // GetSimpleExplodeObjectWithResponse request GetSimpleExplodeObjectWithResponse(ctx context.Context, param Object, reqEditors ...RequestEditorFn) (*GetSimpleExplodeObjectResponse, error) - // GetSimpleNoExplodeArray request + // GetSimpleNoExplodeArrayWithResponse request GetSimpleNoExplodeArrayWithResponse(ctx context.Context, param []int32, reqEditors ...RequestEditorFn) (*GetSimpleNoExplodeArrayResponse, error) - // GetSimpleNoExplodeObject request + // GetSimpleNoExplodeObjectWithResponse request GetSimpleNoExplodeObjectWithResponse(ctx context.Context, param Object, reqEditors ...RequestEditorFn) (*GetSimpleNoExplodeObjectResponse, error) - // GetSimplePrimitive request + // GetSimplePrimitiveWithResponse request GetSimplePrimitiveWithResponse(ctx context.Context, param int32, reqEditors ...RequestEditorFn) (*GetSimplePrimitiveResponse, error) - // GetStartingWithNumber request + // GetStartingWithNumberWithResponse request GetStartingWithNumberWithResponse(ctx context.Context, n1param string, reqEditors ...RequestEditorFn) (*GetStartingWithNumberResponse, error) } @@ -2746,10 +2770,10 @@ func (w *ServerInterfaceWrapper) GetContentObject(ctx echo.Context) error { err = json.Unmarshal([]byte(ctx.Param("param")), ¶m) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshalling parameter 'param' as JSON") + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter 'param' as JSON") } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetContentObject(ctx, param) return err } @@ -2764,7 +2788,7 @@ func (w *ServerInterfaceWrapper) GetCookie(ctx echo.Context) error { if cookie, err := ctx.Cookie("p"); err == nil { var value int32 - err = runtime.BindStyledParameterWithLocation("simple", false, "p", runtime.ParamLocationCookie, cookie.Value, &value) + err = runtime.BindStyledParameterWithOptions("simple", "p", cookie.Value, &value, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationCookie, Explode: false, Required: false, Type: "integer", Format: "int32"}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter p: %s", err)) } @@ -2775,7 +2799,7 @@ func (w *ServerInterfaceWrapper) GetCookie(ctx echo.Context) error { if cookie, err := ctx.Cookie("ep"); err == nil { var value int32 - err = runtime.BindStyledParameterWithLocation("simple", true, "ep", runtime.ParamLocationCookie, cookie.Value, &value) + err = runtime.BindStyledParameterWithOptions("simple", "ep", cookie.Value, &value, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationCookie, Explode: true, Required: false, Type: "integer", Format: "int32"}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter ep: %s", err)) } @@ -2786,7 +2810,7 @@ func (w *ServerInterfaceWrapper) GetCookie(ctx echo.Context) error { if cookie, err := ctx.Cookie("ea"); err == nil { var value []int32 - err = runtime.BindStyledParameterWithLocation("simple", true, "ea", runtime.ParamLocationCookie, cookie.Value, &value) + err = runtime.BindStyledParameterWithOptions("simple", "ea", cookie.Value, &value, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationCookie, Explode: true, Required: false, Type: "array", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter ea: %s", err)) } @@ -2797,7 +2821,7 @@ func (w *ServerInterfaceWrapper) GetCookie(ctx echo.Context) error { if cookie, err := ctx.Cookie("a"); err == nil { var value []int32 - err = runtime.BindStyledParameterWithLocation("simple", false, "a", runtime.ParamLocationCookie, cookie.Value, &value) + err = runtime.BindStyledParameterWithOptions("simple", "a", cookie.Value, &value, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationCookie, Explode: false, Required: false, Type: "array", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter a: %s", err)) } @@ -2808,7 +2832,7 @@ func (w *ServerInterfaceWrapper) GetCookie(ctx echo.Context) error { if cookie, err := ctx.Cookie("eo"); err == nil { var value Object - err = runtime.BindStyledParameterWithLocation("simple", true, "eo", runtime.ParamLocationCookie, cookie.Value, &value) + err = runtime.BindStyledParameterWithOptions("simple", "eo", cookie.Value, &value, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationCookie, Explode: true, Required: false, Type: "", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter eo: %s", err)) } @@ -2819,7 +2843,7 @@ func (w *ServerInterfaceWrapper) GetCookie(ctx echo.Context) error { if cookie, err := ctx.Cookie("o"); err == nil { var value Object - err = runtime.BindStyledParameterWithLocation("simple", false, "o", runtime.ParamLocationCookie, cookie.Value, &value) + err = runtime.BindStyledParameterWithOptions("simple", "o", cookie.Value, &value, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationCookie, Explode: false, Required: false, Type: "", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter o: %s", err)) } @@ -2837,7 +2861,7 @@ func (w *ServerInterfaceWrapper) GetCookie(ctx echo.Context) error { } err = json.Unmarshal([]byte(decoded), &value) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshalling parameter 'co' as JSON") + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter 'co' as JSON") } params.Co = &value @@ -2846,7 +2870,7 @@ func (w *ServerInterfaceWrapper) GetCookie(ctx echo.Context) error { if cookie, err := ctx.Cookie("1s"); err == nil { var value string - err = runtime.BindStyledParameterWithLocation("simple", true, "1s", runtime.ParamLocationCookie, cookie.Value, &value) + err = runtime.BindStyledParameterWithOptions("simple", "1s", cookie.Value, &value, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationCookie, Explode: true, Required: false, Type: "string", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter 1s: %s", err)) } @@ -2854,7 +2878,7 @@ func (w *ServerInterfaceWrapper) GetCookie(ctx echo.Context) error { } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetCookie(ctx, params) return err } @@ -2867,12 +2891,12 @@ func (w *ServerInterfaceWrapper) EnumParams(ctx echo.Context) error { var params EnumParamsParams // ------------- Optional query parameter "enumPathParam" ------------- - err = runtime.BindQueryParameter("form", true, false, "enumPathParam", ctx.QueryParams(), ¶ms.EnumPathParam) + err = runtime.BindQueryParameterWithOptions("form", true, false, "enumPathParam", ctx.QueryParams(), ¶ms.EnumPathParam, runtime.BindQueryParameterOptions{Type: "integer", Format: "int32"}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter enumPathParam: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.EnumParams(ctx, params) return err } @@ -2893,7 +2917,7 @@ func (w *ServerInterfaceWrapper) GetHeader(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for X-Primitive, got %d", n)) } - err = runtime.BindStyledParameterWithLocation("simple", false, "X-Primitive", runtime.ParamLocationHeader, valueList[0], &XPrimitive) + err = runtime.BindStyledParameterWithOptions("simple", "X-Primitive", valueList[0], &XPrimitive, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false, Type: "integer", Format: "int32"}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter X-Primitive: %s", err)) } @@ -2908,7 +2932,7 @@ func (w *ServerInterfaceWrapper) GetHeader(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for X-Primitive-Exploded, got %d", n)) } - err = runtime.BindStyledParameterWithLocation("simple", true, "X-Primitive-Exploded", runtime.ParamLocationHeader, valueList[0], &XPrimitiveExploded) + err = runtime.BindStyledParameterWithOptions("simple", "X-Primitive-Exploded", valueList[0], &XPrimitiveExploded, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: true, Required: false, Type: "integer", Format: "int32"}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter X-Primitive-Exploded: %s", err)) } @@ -2923,7 +2947,7 @@ func (w *ServerInterfaceWrapper) GetHeader(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for X-Array-Exploded, got %d", n)) } - err = runtime.BindStyledParameterWithLocation("simple", true, "X-Array-Exploded", runtime.ParamLocationHeader, valueList[0], &XArrayExploded) + err = runtime.BindStyledParameterWithOptions("simple", "X-Array-Exploded", valueList[0], &XArrayExploded, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: true, Required: false, Type: "array", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter X-Array-Exploded: %s", err)) } @@ -2938,7 +2962,7 @@ func (w *ServerInterfaceWrapper) GetHeader(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for X-Array, got %d", n)) } - err = runtime.BindStyledParameterWithLocation("simple", false, "X-Array", runtime.ParamLocationHeader, valueList[0], &XArray) + err = runtime.BindStyledParameterWithOptions("simple", "X-Array", valueList[0], &XArray, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false, Type: "array", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter X-Array: %s", err)) } @@ -2953,7 +2977,7 @@ func (w *ServerInterfaceWrapper) GetHeader(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for X-Object-Exploded, got %d", n)) } - err = runtime.BindStyledParameterWithLocation("simple", true, "X-Object-Exploded", runtime.ParamLocationHeader, valueList[0], &XObjectExploded) + err = runtime.BindStyledParameterWithOptions("simple", "X-Object-Exploded", valueList[0], &XObjectExploded, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: true, Required: false, Type: "", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter X-Object-Exploded: %s", err)) } @@ -2968,7 +2992,7 @@ func (w *ServerInterfaceWrapper) GetHeader(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for X-Object, got %d", n)) } - err = runtime.BindStyledParameterWithLocation("simple", false, "X-Object", runtime.ParamLocationHeader, valueList[0], &XObject) + err = runtime.BindStyledParameterWithOptions("simple", "X-Object", valueList[0], &XObject, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false, Type: "", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter X-Object: %s", err)) } @@ -2985,7 +3009,7 @@ func (w *ServerInterfaceWrapper) GetHeader(ctx echo.Context) error { err = json.Unmarshal([]byte(valueList[0]), &XComplexObject) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshalling parameter 'X-Complex-Object' as JSON") + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter 'X-Complex-Object' as JSON") } params.XComplexObject = &XComplexObject @@ -2998,7 +3022,7 @@ func (w *ServerInterfaceWrapper) GetHeader(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for 1-Starting-With-Number, got %d", n)) } - err = runtime.BindStyledParameterWithLocation("simple", false, "1-Starting-With-Number", runtime.ParamLocationHeader, valueList[0], &N1StartingWithNumber) + err = runtime.BindStyledParameterWithOptions("simple", "1-Starting-With-Number", valueList[0], &N1StartingWithNumber, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false, Type: "string", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter 1-Starting-With-Number: %s", err)) } @@ -3006,7 +3030,7 @@ func (w *ServerInterfaceWrapper) GetHeader(ctx echo.Context) error { params.N1StartingWithNumber = &N1StartingWithNumber } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetHeader(ctx, params) return err } @@ -3017,12 +3041,12 @@ func (w *ServerInterfaceWrapper) GetLabelExplodeArray(ctx echo.Context) error { // ------------- Path parameter "param" ------------- var param []int32 - err = runtime.BindStyledParameterWithLocation("label", true, "param", runtime.ParamLocationPath, ctx.Param("param"), ¶m) + err = runtime.BindStyledParameterWithOptions("label", "param", ctx.Param("param"), ¶m, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: true, Required: true, Type: "array", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter param: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetLabelExplodeArray(ctx, param) return err } @@ -3033,12 +3057,12 @@ func (w *ServerInterfaceWrapper) GetLabelExplodeObject(ctx echo.Context) error { // ------------- Path parameter "param" ------------- var param Object - err = runtime.BindStyledParameterWithLocation("label", true, "param", runtime.ParamLocationPath, ctx.Param("param"), ¶m) + err = runtime.BindStyledParameterWithOptions("label", "param", ctx.Param("param"), ¶m, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: true, Required: true, Type: "", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter param: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetLabelExplodeObject(ctx, param) return err } @@ -3049,12 +3073,12 @@ func (w *ServerInterfaceWrapper) GetLabelNoExplodeArray(ctx echo.Context) error // ------------- Path parameter "param" ------------- var param []int32 - err = runtime.BindStyledParameterWithLocation("label", false, "param", runtime.ParamLocationPath, ctx.Param("param"), ¶m) + err = runtime.BindStyledParameterWithOptions("label", "param", ctx.Param("param"), ¶m, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "array", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter param: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetLabelNoExplodeArray(ctx, param) return err } @@ -3065,12 +3089,12 @@ func (w *ServerInterfaceWrapper) GetLabelNoExplodeObject(ctx echo.Context) error // ------------- Path parameter "param" ------------- var param Object - err = runtime.BindStyledParameterWithLocation("label", false, "param", runtime.ParamLocationPath, ctx.Param("param"), ¶m) + err = runtime.BindStyledParameterWithOptions("label", "param", ctx.Param("param"), ¶m, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter param: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetLabelNoExplodeObject(ctx, param) return err } @@ -3081,12 +3105,12 @@ func (w *ServerInterfaceWrapper) GetMatrixExplodeArray(ctx echo.Context) error { // ------------- Path parameter "id" ------------- var id []int32 - err = runtime.BindStyledParameterWithLocation("matrix", true, "id", runtime.ParamLocationPath, ctx.Param("id"), &id) + err = runtime.BindStyledParameterWithOptions("matrix", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: true, Required: true, Type: "array", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetMatrixExplodeArray(ctx, id) return err } @@ -3097,12 +3121,12 @@ func (w *ServerInterfaceWrapper) GetMatrixExplodeObject(ctx echo.Context) error // ------------- Path parameter "id" ------------- var id Object - err = runtime.BindStyledParameterWithLocation("matrix", true, "id", runtime.ParamLocationPath, ctx.Param("id"), &id) + err = runtime.BindStyledParameterWithOptions("matrix", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: true, Required: true, Type: "", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetMatrixExplodeObject(ctx, id) return err } @@ -3113,12 +3137,12 @@ func (w *ServerInterfaceWrapper) GetMatrixNoExplodeArray(ctx echo.Context) error // ------------- Path parameter "id" ------------- var id []int32 - err = runtime.BindStyledParameterWithLocation("matrix", false, "id", runtime.ParamLocationPath, ctx.Param("id"), &id) + err = runtime.BindStyledParameterWithOptions("matrix", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "array", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetMatrixNoExplodeArray(ctx, id) return err } @@ -3129,12 +3153,12 @@ func (w *ServerInterfaceWrapper) GetMatrixNoExplodeObject(ctx echo.Context) erro // ------------- Path parameter "id" ------------- var id Object - err = runtime.BindStyledParameterWithLocation("matrix", false, "id", runtime.ParamLocationPath, ctx.Param("id"), &id) + err = runtime.BindStyledParameterWithOptions("matrix", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetMatrixNoExplodeObject(ctx, id) return err } @@ -3147,7 +3171,7 @@ func (w *ServerInterfaceWrapper) GetPassThrough(ctx echo.Context) error { param = ctx.Param("param") - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetPassThrough(ctx, param) return err } @@ -3160,12 +3184,12 @@ func (w *ServerInterfaceWrapper) GetDeepObject(ctx echo.Context) error { var params GetDeepObjectParams // ------------- Required query parameter "deepObj" ------------- - err = runtime.BindQueryParameter("deepObject", true, true, "deepObj", ctx.QueryParams(), ¶ms.DeepObj) + err = runtime.BindQueryParameterWithOptions("deepObject", true, true, "deepObj", ctx.QueryParams(), ¶ms.DeepObj, runtime.BindQueryParameterOptions{Type: "", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter deepObj: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetDeepObject(ctx, params) return err } @@ -3178,49 +3202,49 @@ func (w *ServerInterfaceWrapper) GetQueryForm(ctx echo.Context) error { var params GetQueryFormParams // ------------- Optional query parameter "ea" ------------- - err = runtime.BindQueryParameter("form", true, false, "ea", ctx.QueryParams(), ¶ms.Ea) + err = runtime.BindQueryParameterWithOptions("form", true, false, "ea", ctx.QueryParams(), ¶ms.Ea, runtime.BindQueryParameterOptions{Type: "array", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter ea: %s", err)) } // ------------- Optional query parameter "a" ------------- - err = runtime.BindQueryParameter("form", false, false, "a", ctx.QueryParams(), ¶ms.A) + err = runtime.BindQueryParameterWithOptions("form", false, false, "a", ctx.QueryParams(), ¶ms.A, runtime.BindQueryParameterOptions{Type: "array", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter a: %s", err)) } // ------------- Optional query parameter "eo" ------------- - err = runtime.BindQueryParameter("form", true, false, "eo", ctx.QueryParams(), ¶ms.Eo) + err = runtime.BindQueryParameterWithOptions("form", true, false, "eo", ctx.QueryParams(), ¶ms.Eo, runtime.BindQueryParameterOptions{Type: "", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter eo: %s", err)) } // ------------- Optional query parameter "o" ------------- - err = runtime.BindQueryParameter("form", false, false, "o", ctx.QueryParams(), ¶ms.O) + err = runtime.BindQueryParameterWithOptions("form", false, false, "o", ctx.QueryParams(), ¶ms.O, runtime.BindQueryParameterOptions{Type: "", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter o: %s", err)) } // ------------- Optional query parameter "ep" ------------- - err = runtime.BindQueryParameter("form", true, false, "ep", ctx.QueryParams(), ¶ms.Ep) + err = runtime.BindQueryParameterWithOptions("form", true, false, "ep", ctx.QueryParams(), ¶ms.Ep, runtime.BindQueryParameterOptions{Type: "integer", Format: "int32"}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter ep: %s", err)) } // ------------- Optional query parameter "p" ------------- - err = runtime.BindQueryParameter("form", false, false, "p", ctx.QueryParams(), ¶ms.P) + err = runtime.BindQueryParameterWithOptions("form", false, false, "p", ctx.QueryParams(), ¶ms.P, runtime.BindQueryParameterOptions{Type: "integer", Format: "int32"}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter p: %s", err)) } // ------------- Optional query parameter "ps" ------------- - err = runtime.BindQueryParameter("form", true, false, "ps", ctx.QueryParams(), ¶ms.Ps) + err = runtime.BindQueryParameterWithOptions("form", true, false, "ps", ctx.QueryParams(), ¶ms.Ps, runtime.BindQueryParameterOptions{Type: "string", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter ps: %s", err)) } @@ -3232,7 +3256,7 @@ func (w *ServerInterfaceWrapper) GetQueryForm(ctx echo.Context) error { var value ComplexObject err = json.Unmarshal([]byte(paramValue), &value) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshalling parameter 'co' as JSON") + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter 'co' as JSON") } params.Co = &value @@ -3240,12 +3264,12 @@ func (w *ServerInterfaceWrapper) GetQueryForm(ctx echo.Context) error { // ------------- Optional query parameter "1s" ------------- - err = runtime.BindQueryParameter("form", true, false, "1s", ctx.QueryParams(), ¶ms.N1s) + err = runtime.BindQueryParameterWithOptions("form", true, false, "1s", ctx.QueryParams(), ¶ms.N1s, runtime.BindQueryParameterOptions{Type: "string", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter 1s: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetQueryForm(ctx, params) return err } @@ -3256,12 +3280,12 @@ func (w *ServerInterfaceWrapper) GetSimpleExplodeArray(ctx echo.Context) error { // ------------- Path parameter "param" ------------- var param []int32 - err = runtime.BindStyledParameterWithLocation("simple", true, "param", runtime.ParamLocationPath, ctx.Param("param"), ¶m) + err = runtime.BindStyledParameterWithOptions("simple", "param", ctx.Param("param"), ¶m, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: true, Required: true, Type: "array", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter param: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetSimpleExplodeArray(ctx, param) return err } @@ -3272,12 +3296,12 @@ func (w *ServerInterfaceWrapper) GetSimpleExplodeObject(ctx echo.Context) error // ------------- Path parameter "param" ------------- var param Object - err = runtime.BindStyledParameterWithLocation("simple", true, "param", runtime.ParamLocationPath, ctx.Param("param"), ¶m) + err = runtime.BindStyledParameterWithOptions("simple", "param", ctx.Param("param"), ¶m, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: true, Required: true, Type: "", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter param: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetSimpleExplodeObject(ctx, param) return err } @@ -3288,12 +3312,12 @@ func (w *ServerInterfaceWrapper) GetSimpleNoExplodeArray(ctx echo.Context) error // ------------- Path parameter "param" ------------- var param []int32 - err = runtime.BindStyledParameterWithLocation("simple", false, "param", runtime.ParamLocationPath, ctx.Param("param"), ¶m) + err = runtime.BindStyledParameterWithOptions("simple", "param", ctx.Param("param"), ¶m, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "array", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter param: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetSimpleNoExplodeArray(ctx, param) return err } @@ -3304,12 +3328,12 @@ func (w *ServerInterfaceWrapper) GetSimpleNoExplodeObject(ctx echo.Context) erro // ------------- Path parameter "param" ------------- var param Object - err = runtime.BindStyledParameterWithLocation("simple", false, "param", runtime.ParamLocationPath, ctx.Param("param"), ¶m) + err = runtime.BindStyledParameterWithOptions("simple", "param", ctx.Param("param"), ¶m, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter param: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetSimpleNoExplodeObject(ctx, param) return err } @@ -3320,12 +3344,12 @@ func (w *ServerInterfaceWrapper) GetSimplePrimitive(ctx echo.Context) error { // ------------- Path parameter "param" ------------- var param int32 - err = runtime.BindStyledParameterWithLocation("simple", false, "param", runtime.ParamLocationPath, ctx.Param("param"), ¶m) + err = runtime.BindStyledParameterWithOptions("simple", "param", ctx.Param("param"), ¶m, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "integer", Format: "int32"}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter param: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetSimplePrimitive(ctx, param) return err } @@ -3338,7 +3362,7 @@ func (w *ServerInterfaceWrapper) GetStartingWithNumber(ctx echo.Context) error { n1param = ctx.Param("1param") - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetStartingWithNumber(ctx, n1param) return err } @@ -3425,16 +3449,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -3452,7 +3476,7 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } @@ -3466,12 +3490,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/internal/test/parameters/parameters_test.go b/internal/test/parameters/parameters_test.go index 3e3fb5408b..0635301dfa 100644 --- a/internal/test/parameters/parameters_test.go +++ b/internal/test/parameters/parameters_test.go @@ -7,9 +7,8 @@ import ( "net/http/httptest" "testing" - "github.com/deepmap/oapi-codegen/pkg/testutil" + "github.com/oapi-codegen/testutil" "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -243,7 +242,6 @@ func (t *testServer) EnumParams(ctx echo.Context, params EnumParamsParams) error func TestParameterBinding(t *testing.T) { var ts testServer e := echo.New() - e.Use(middleware.Logger()) RegisterHandlers(e, &ts) expectedObject := Object{ @@ -267,7 +265,7 @@ func TestParameterBinding(t *testing.T) { // Check the passthrough case // (GET /passThrough/{param}) - result := testutil.NewRequest().Get("/passThrough/some%20string").Go(t, e) + result := testutil.NewRequest().Get("/passThrough/some%20string").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) require.NotNil(t, ts.passThrough) assert.EqualValues(t, "some string", *ts.passThrough) @@ -278,93 +276,93 @@ func TestParameterBinding(t *testing.T) { marshaledComplexObject, err := json.Marshal(expectedComplexObject) assert.NoError(t, err) q := fmt.Sprintf("/contentObject/%s", string(marshaledComplexObject)) - result = testutil.NewRequest().Get(q).Go(t, e) + result = testutil.NewRequest().Get(q).GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedComplexObject, ts.complexObject) ts.reset() // (GET /labelExplodeArray/{.param*}) - result = testutil.NewRequest().Get("/labelExplodeArray/.3.4.5").Go(t, e) + result = testutil.NewRequest().Get("/labelExplodeArray/.3.4.5").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, expectedArray, ts.array) ts.reset() // (GET /labelExplodeObject/{.param*}) - result = testutil.NewRequest().Get("/labelExplodeObject/.role=admin.firstName=Alex").Go(t, e) + result = testutil.NewRequest().Get("/labelExplodeObject/.role=admin.firstName=Alex").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedObject, ts.object) ts.reset() // (GET /labelNoExplodeArray/{.param}) - result = testutil.NewRequest().Get("/labelNoExplodeArray/.3,4,5").Go(t, e) + result = testutil.NewRequest().Get("/labelNoExplodeArray/.3,4,5").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, expectedArray, ts.array) ts.reset() // (GET /labelNoExplodeObject/{.param}) - result = testutil.NewRequest().Get("/labelNoExplodeObject/.role,admin,firstName,Alex").Go(t, e) + result = testutil.NewRequest().Get("/labelNoExplodeObject/.role,admin,firstName,Alex").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedObject, ts.object) ts.reset() // (GET /matrixExplodeArray/{.param*}) uri := "/matrixExplodeArray/;id=3;id=4;id=5" - result = testutil.NewRequest().Get(uri).Go(t, e) + result = testutil.NewRequest().Get(uri).GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, expectedArray, ts.array) ts.reset() // (GET /matrixExplodeObject/{.param*}) uri = "/matrixExplodeObject/;role=admin;firstName=Alex" - result = testutil.NewRequest().Get(uri).Go(t, e) + result = testutil.NewRequest().Get(uri).GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedObject, ts.object) ts.reset() // (GET /matrixNoExplodeArray/{.param}) - result = testutil.NewRequest().Get("/matrixNoExplodeArray/;id=3,4,5").Go(t, e) + result = testutil.NewRequest().Get("/matrixNoExplodeArray/;id=3,4,5").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, expectedArray, ts.array) ts.reset() // (GET /matrixNoExplodeObject/{.param}) - result = testutil.NewRequest().Get("/matrixNoExplodeObject/;id=role,admin,firstName,Alex").Go(t, e) + result = testutil.NewRequest().Get("/matrixNoExplodeObject/;id=role,admin,firstName,Alex").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedObject, ts.object) ts.reset() // (GET /simpleExplodeArray/{param*}) - result = testutil.NewRequest().Get("/simpleExplodeArray/3,4,5").Go(t, e) + result = testutil.NewRequest().Get("/simpleExplodeArray/3,4,5").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, expectedArray, ts.array) ts.reset() // (GET /simpleExplodeObject/{param*}) - result = testutil.NewRequest().Get("/simpleExplodeObject/role=admin,firstName=Alex").Go(t, e) + result = testutil.NewRequest().Get("/simpleExplodeObject/role=admin,firstName=Alex").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedObject, ts.object) ts.reset() // (GET /simpleNoExplodeArray/{param}) - result = testutil.NewRequest().Get("/simpleNoExplodeArray/3,4,5").Go(t, e) + result = testutil.NewRequest().Get("/simpleNoExplodeArray/3,4,5").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, expectedArray, ts.array) ts.reset() // (GET /simpleNoExplodeObject/{param}) - result = testutil.NewRequest().Get("/simpleNoExplodeObject/role,admin,firstName,Alex").Go(t, e) + result = testutil.NewRequest().Get("/simpleNoExplodeObject/role,admin,firstName,Alex").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedObject, ts.object) ts.reset() // (GET /simplePrimitive/{param}) - result = testutil.NewRequest().Get("/simplePrimitive/5").Go(t, e) + result = testutil.NewRequest().Get("/simplePrimitive/5").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedPrimitive, ts.primitive) ts.reset() // (GET /startingWithNumber/{1param}) - result = testutil.NewRequest().Get("/startingWithNumber/foo").Go(t, e) + result = testutil.NewRequest().Get("/startingWithNumber/foo").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedN1Param, ts.n1param) ts.reset() @@ -373,56 +371,56 @@ func TestParameterBinding(t *testing.T) { // (GET /queryForm) // unexploded array - result = testutil.NewRequest().Get("/queryForm?a=3,4,5").Go(t, e) + result = testutil.NewRequest().Get("/queryForm?a=3,4,5").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, expectedArray, ts.array) ts.reset() // exploded array - result = testutil.NewRequest().Get("/queryForm?ea=3&ea=4&ea=5").Go(t, e) + result = testutil.NewRequest().Get("/queryForm?ea=3&ea=4&ea=5").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, expectedArray, ts.array) ts.reset() // unexploded object - result = testutil.NewRequest().Get("/queryForm?o=role,admin,firstName,Alex").Go(t, e) + result = testutil.NewRequest().Get("/queryForm?o=role,admin,firstName,Alex").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedObject, ts.object) ts.reset() // exploded object - result = testutil.NewRequest().Get("/queryForm?role=admin&firstName=Alex").Go(t, e) + result = testutil.NewRequest().Get("/queryForm?role=admin&firstName=Alex").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedObject, ts.object) ts.reset() // exploded primitive - result = testutil.NewRequest().Get("/queryForm?ep=5").Go(t, e) + result = testutil.NewRequest().Get("/queryForm?ep=5").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedPrimitive, ts.primitive) ts.reset() // unexploded primitive - result = testutil.NewRequest().Get("/queryForm?p=5").Go(t, e) + result = testutil.NewRequest().Get("/queryForm?p=5").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedPrimitive, ts.primitive) ts.reset() // primitive string within reserved char, i.e., ';' escaped to '%3B' - result = testutil.NewRequest().Get("/queryForm?ps=123%3B456").Go(t, e) + result = testutil.NewRequest().Get("/queryForm?ps=123%3B456").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedPrimitiveString, ts.primitiveString) ts.reset() // complex object q = fmt.Sprintf("/queryForm?co=%s", string(marshaledComplexObject)) - result = testutil.NewRequest().Get(q).Go(t, e) + result = testutil.NewRequest().Get(q).GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedComplexObject, ts.complexObject) ts.reset() // starting with number - result = testutil.NewRequest().Get("/queryForm?1s=foo").Go(t, e) + result = testutil.NewRequest().Get("/queryForm?1s=foo").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedN1Param, ts.n1param) ts.reset() @@ -430,7 +428,7 @@ func TestParameterBinding(t *testing.T) { // complex object via deepObject do := `deepObj[Id]=12345&deepObj[IsAdmin]=true&deepObj[Object][firstName]=Alex&deepObj[Object][role]=admin` q = "/queryDeepObject?" + do - result = testutil.NewRequest().Get(q).Go(t, e) + result = testutil.NewRequest().Get(q).GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedComplexObject, ts.complexObject) ts.reset() @@ -438,80 +436,80 @@ func TestParameterBinding(t *testing.T) { // ---------------------- Test Header Query Parameters -------------------- // unexploded header primitive. - result = testutil.NewRequest().WithHeader("X-Primitive", "5").Get("/header").Go(t, e) + result = testutil.NewRequest().WithHeader("X-Primitive", "5").Get("/header").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedPrimitive, ts.primitive) ts.reset() // exploded header primitive. - result = testutil.NewRequest().WithHeader("X-Primitive-Exploded", "5").Get("/header").Go(t, e) + result = testutil.NewRequest().WithHeader("X-Primitive-Exploded", "5").Get("/header").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedPrimitive, ts.primitive) ts.reset() // unexploded header array - result = testutil.NewRequest().WithHeader("X-Array", "3,4,5").Get("/header").Go(t, e) + result = testutil.NewRequest().WithHeader("X-Array", "3,4,5").Get("/header").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, expectedArray, ts.array) ts.reset() // exploded header array - result = testutil.NewRequest().WithHeader("X-Array-Exploded", "3,4,5").Get("/header").Go(t, e) + result = testutil.NewRequest().WithHeader("X-Array-Exploded", "3,4,5").Get("/header").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, expectedArray, ts.array) ts.reset() // unexploded header object result = testutil.NewRequest().WithHeader("X-Object", - "role,admin,firstName,Alex").Get("/header").Go(t, e) + "role,admin,firstName,Alex").Get("/header").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedObject, ts.object) ts.reset() // exploded header object result = testutil.NewRequest().WithHeader("X-Object-Exploded", - "role=admin,firstName=Alex").Get("/header").Go(t, e) + "role=admin,firstName=Alex").Get("/header").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedObject, ts.object) ts.reset() // complex object result = testutil.NewRequest().WithHeader("X-Complex-Object", - string(marshaledComplexObject)).Get("/header").Go(t, e) + string(marshaledComplexObject)).Get("/header").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedComplexObject, ts.complexObject) ts.reset() // starting with number result = testutil.NewRequest().WithHeader("1-Starting-With-Number", - "foo").Get("/header").Go(t, e) + "foo").Get("/header").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedN1Param, ts.n1param) ts.reset() // ------------------------- Test Cookie Parameters ------------------------ - result = testutil.NewRequest().WithCookieNameValue("p", "5").Get("/cookie").Go(t, e) + result = testutil.NewRequest().WithCookieNameValue("p", "5").Get("/cookie").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedPrimitive, ts.primitive) ts.reset() - result = testutil.NewRequest().WithCookieNameValue("ep", "5").Get("/cookie").Go(t, e) + result = testutil.NewRequest().WithCookieNameValue("ep", "5").Get("/cookie").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedPrimitive, ts.primitive) ts.reset() - result = testutil.NewRequest().WithCookieNameValue("a", "3,4,5").Get("/cookie").Go(t, e) + result = testutil.NewRequest().WithCookieNameValue("a", "3,4,5").Get("/cookie").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, expectedArray, ts.array) ts.reset() result = testutil.NewRequest().WithCookieNameValue( - "o", "role,admin,firstName,Alex").Get("/cookie").Go(t, e) + "o", "role,admin,firstName,Alex").Get("/cookie").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedObject, ts.object) ts.reset() - result = testutil.NewRequest().WithCookieNameValue("1s", "foo").Get("/cookie").Go(t, e) + result = testutil.NewRequest().WithCookieNameValue("1s", "foo").Get("/cookie").GoWithHTTPHandler(t, e) assert.Equal(t, http.StatusOK, result.Code()) assert.EqualValues(t, &expectedN1Param, ts.n1param) ts.reset() @@ -527,7 +525,6 @@ func doRequest(t *testing.T, e *echo.Echo, code int, req *http.Request) *httptes func TestClientPathParams(t *testing.T) { var ts testServer e := echo.New() - e.Use(middleware.Logger()) RegisterHandlers(e, &ts) server := "http://example.com" @@ -651,7 +648,6 @@ func TestClientPathParams(t *testing.T) { func TestClientQueryParams(t *testing.T) { var ts testServer e := echo.New() - e.Use(middleware.Logger()) RegisterHandlers(e, &ts) server := "http://example.com" diff --git a/internal/test/schemas/config.yaml b/internal/test/schemas/config.yaml index 58b5060d37..a054e8daf8 100644 --- a/internal/test/schemas/config.yaml +++ b/internal/test/schemas/config.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../configuration-schema.json package: schemas generate: echo-server: true @@ -5,3 +6,5 @@ generate: models: true embedded-spec: true output: schemas.gen.go +output-options: + skip-prune: true diff --git a/internal/test/schemas/doc.go b/internal/test/schemas/doc.go index b3a7d25c05..a4147ee5af 100644 --- a/internal/test/schemas/doc.go +++ b/internal/test/schemas/doc.go @@ -1,3 +1,3 @@ package schemas -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml schemas.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml schemas.yaml diff --git a/internal/test/schemas/schemas.gen.go b/internal/test/schemas/schemas.gen.go index b6a9f2086b..9c0871cb34 100644 --- a/internal/test/schemas/schemas.gen.go +++ b/internal/test/schemas/schemas.gen.go @@ -1,6 +1,6 @@ // Package schemas provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package schemas import ( @@ -19,9 +19,9 @@ import ( "gopkg.in/yaml.v2" - "github.com/deepmap/oapi-codegen/pkg/runtime" "github.com/getkin/kin-openapi/openapi3" "github.com/labstack/echo/v4" + "github.com/oapi-codegen/runtime" ) const ( @@ -34,6 +34,18 @@ const ( Second EnumInObjInArrayVal = "second" ) +// Valid indicates whether the value is a known member of the EnumInObjInArrayVal enum. +func (e EnumInObjInArrayVal) Valid() bool { + switch e { + case First: + return true + case Second: + return true + default: + return false + } +} + // N5StartsWithNumber This schema name starts with a number type N5StartsWithNumber = map[string]interface{} @@ -48,6 +60,24 @@ type AnyType2 = interface{} // CustomStringType defines model for CustomStringType. type CustomStringType = string +// DeprecatedProperty defines model for DeprecatedProperty. +type DeprecatedProperty struct { + // NewProp Use this now! + NewProp string `json:"newProp"` + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + OldProp1 *string `json:"oldProp1,omitempty"` + + // OldProp2 It used to do this and that + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + OldProp2 *string `json:"oldProp2,omitempty"` + // Deprecated: Use NewProp instead! + OldProp3 *string `json:"oldProp3,omitempty"` + + // OldProp4 It used to do this and that + // Deprecated: Use NewProp instead! + OldProp4 *string `json:"oldProp4,omitempty"` +} + // EnumInObjInArray defines model for EnumInObjInArray. type EnumInObjInArray = []struct { Val *EnumInObjInArrayVal `json:"val,omitempty"` @@ -62,11 +92,22 @@ type GenericObject = map[string]interface{} // NullableProperties defines model for NullableProperties. type NullableProperties struct { Optional *string `json:"optional,omitempty"` - OptionalAndNullable *string `json:"optionalAndNullable"` + OptionalAndNullable *string `json:"optionalAndNullable,omitempty"` Required string `json:"required"` RequiredAndNullable *string `json:"requiredAndNullable"` } +// OuterTypeWithAnonymousInner defines model for OuterTypeWithAnonymousInner. +type OuterTypeWithAnonymousInner struct { + Inner InnerRenamedAnonymousObject `json:"inner"` + Name string `json:"name"` +} + +// InnerRenamedAnonymousObject defines model for . +type InnerRenamedAnonymousObject struct { + Id int `json:"id"` +} + // StringInPath defines model for StringInPath. type StringInPath = string @@ -160,10 +201,13 @@ type ClientInterface interface { // EnsureEverythingIsReferenced request EnsureEverythingIsReferenced(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // Issue1051 request + Issue1051(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // Issue127 request Issue127(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - // Issue185 request with any body + // Issue185WithBody request with any body Issue185WithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) Issue185(ctx context.Context, body Issue185JSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -180,10 +224,13 @@ type ClientInterface interface { // Issue41 request Issue41(ctx context.Context, n1param N5StartsWithNumber, reqEditors ...RequestEditorFn) (*http.Response, error) - // Issue9 request with any body + // Issue9WithBody request with any body Issue9WithBody(ctx context.Context, params *Issue9Params, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) Issue9(ctx context.Context, params *Issue9Params, body Issue9JSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // Issue975 request + Issue975(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) } func (c *Client) EnsureEverythingIsReferenced(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -198,6 +245,18 @@ func (c *Client) EnsureEverythingIsReferenced(ctx context.Context, reqEditors .. return c.Client.Do(req) } +func (c *Client) Issue1051(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewIssue1051Request(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) Issue127(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewIssue127Request(c.Server) if err != nil { @@ -306,6 +365,18 @@ func (c *Client) Issue9(ctx context.Context, params *Issue9Params, body Issue9JS return c.Client.Do(req) } +func (c *Client) Issue975(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewIssue975Request(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + // NewEnsureEverythingIsReferencedRequest generates requests for EnsureEverythingIsReferenced func NewEnsureEverythingIsReferencedRequest(server string) (*http.Request, error) { var err error @@ -333,6 +404,33 @@ func NewEnsureEverythingIsReferencedRequest(server string) (*http.Request, error return req, nil } +// NewIssue1051Request generates requests for Issue1051 +func NewIssue1051Request(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/issues/1051") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewIssue127Request generates requests for Issue127 func NewIssue127Request(server string) (*http.Request, error) { var err error @@ -406,7 +504,7 @@ func NewIssue209Request(server string, str StringInPath) (*http.Request, error) var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "str", runtime.ParamLocationPath, str) + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "str", str, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) if err != nil { return nil, err } @@ -440,7 +538,7 @@ func NewIssue30Request(server string, pFallthrough string) (*http.Request, error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "fallthrough", runtime.ParamLocationPath, pFallthrough) + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "fallthrough", pFallthrough, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) if err != nil { return nil, err } @@ -501,7 +599,7 @@ func NewIssue41Request(server string, n1param N5StartsWithNumber) (*http.Request var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "1param", runtime.ParamLocationPath, n1param) + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "1param", n1param, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "object", Format: ""}) if err != nil { return nil, err } @@ -559,21 +657,23 @@ func NewIssue9RequestWithBody(server string, params *Issue9Params, contentType s return nil, err } - queryValues := queryURL.Query() + if params != nil { + queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "foo", runtime.ParamLocationQuery, params.Foo); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "foo", params.Foo, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } - } - queryURL.RawQuery = queryValues.Encode() + queryURL.RawQuery = queryValues.Encode() + } req, err := http.NewRequest("GET", queryURL.String(), body) if err != nil { @@ -585,6 +685,33 @@ func NewIssue9RequestWithBody(server string, params *Issue9Params, contentType s return req, nil } +// NewIssue975Request generates requests for Issue975 +func NewIssue975Request(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/issues/975") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { for _, r := range c.RequestEditors { if err := r(ctx, req); err != nil { @@ -628,33 +755,39 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { - // EnsureEverythingIsReferenced request + // EnsureEverythingIsReferencedWithResponse request EnsureEverythingIsReferencedWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) - // Issue127 request + // Issue1051WithResponse request + Issue1051WithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*Issue1051Response, error) + + // Issue127WithResponse request Issue127WithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*Issue127Response, error) - // Issue185 request with any body + // Issue185WithBodyWithResponse request with any body Issue185WithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*Issue185Response, error) Issue185WithResponse(ctx context.Context, body Issue185JSONRequestBody, reqEditors ...RequestEditorFn) (*Issue185Response, error) - // Issue209 request + // Issue209WithResponse request Issue209WithResponse(ctx context.Context, str StringInPath, reqEditors ...RequestEditorFn) (*Issue209Response, error) - // Issue30 request + // Issue30WithResponse request Issue30WithResponse(ctx context.Context, pFallthrough string, reqEditors ...RequestEditorFn) (*Issue30Response, error) - // GetIssues375 request + // GetIssues375WithResponse request GetIssues375WithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetIssues375Response, error) - // Issue41 request + // Issue41WithResponse request Issue41WithResponse(ctx context.Context, n1param N5StartsWithNumber, reqEditors ...RequestEditorFn) (*Issue41Response, error) - // Issue9 request with any body + // Issue9WithBodyWithResponse request with any body Issue9WithBodyWithResponse(ctx context.Context, params *Issue9Params, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*Issue9Response, error) Issue9WithResponse(ctx context.Context, params *Issue9Params, body Issue9JSONRequestBody, reqEditors ...RequestEditorFn) (*Issue9Response, error) + + // Issue975WithResponse request + Issue975WithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*Issue975Response, error) } type EnsureEverythingIsReferencedResponse struct { @@ -687,6 +820,29 @@ func (r EnsureEverythingIsReferencedResponse) StatusCode() int { return 0 } +type Issue1051Response struct { + Body []byte + HTTPResponse *http.Response + JSON200 *map[string]interface{} + ApplicationvndSomethingV1JSON200 *map[string]interface{} +} + +// Status returns HTTPResponse.Status +func (r Issue1051Response) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r Issue1051Response) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type Issue127Response struct { Body []byte HTTPResponse *http.Response @@ -839,6 +995,28 @@ func (r Issue9Response) StatusCode() int { return 0 } +type Issue975Response struct { + Body []byte + HTTPResponse *http.Response + JSON200 *DeprecatedProperty +} + +// Status returns HTTPResponse.Status +func (r Issue975Response) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r Issue975Response) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + // EnsureEverythingIsReferencedWithResponse request returning *EnsureEverythingIsReferencedResponse func (c *ClientWithResponses) EnsureEverythingIsReferencedWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) { rsp, err := c.EnsureEverythingIsReferenced(ctx, reqEditors...) @@ -848,6 +1026,15 @@ func (c *ClientWithResponses) EnsureEverythingIsReferencedWithResponse(ctx conte return ParseEnsureEverythingIsReferencedResponse(rsp) } +// Issue1051WithResponse request returning *Issue1051Response +func (c *ClientWithResponses) Issue1051WithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*Issue1051Response, error) { + rsp, err := c.Issue1051(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseIssue1051Response(rsp) +} + // Issue127WithResponse request returning *Issue127Response func (c *ClientWithResponses) Issue127WithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*Issue127Response, error) { rsp, err := c.Issue127(ctx, reqEditors...) @@ -927,6 +1114,15 @@ func (c *ClientWithResponses) Issue9WithResponse(ctx context.Context, params *Is return ParseIssue9Response(rsp) } +// Issue975WithResponse request returning *Issue975Response +func (c *ClientWithResponses) Issue975WithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*Issue975Response, error) { + rsp, err := c.Issue975(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseIssue975Response(rsp) +} + // ParseEnsureEverythingIsReferencedResponse parses an HTTP response from a EnsureEverythingIsReferencedWithResponse call func ParseEnsureEverythingIsReferencedResponse(rsp *http.Response) (*EnsureEverythingIsReferencedResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -961,6 +1157,39 @@ func ParseEnsureEverythingIsReferencedResponse(rsp *http.Response) (*EnsureEvery return response, nil } +// ParseIssue1051Response parses an HTTP response from a Issue1051WithResponse call +func ParseIssue1051Response(rsp *http.Response) (*Issue1051Response, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &Issue1051Response{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case rsp.Header.Get("Content-Type") == "application/json" && rsp.StatusCode == 200: + var dest map[string]interface{} + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case rsp.Header.Get("Content-Type") == "application/vnd.something.v1+json" && rsp.StatusCode == 200: + var dest map[string]interface{} + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.ApplicationvndSomethingV1JSON200 = &dest + + } + + return response, nil +} + // ParseIssue127Response parses an HTTP response from a Issue127WithResponse call func ParseIssue127Response(rsp *http.Response) (*Issue127Response, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -1120,12 +1349,41 @@ func ParseIssue9Response(rsp *http.Response) (*Issue9Response, error) { return response, nil } +// ParseIssue975Response parses an HTTP response from a Issue975WithResponse call +func ParseIssue975Response(rsp *http.Response) (*Issue975Response, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &Issue975Response{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest DeprecatedProperty + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ServerInterface represents all server handlers. type ServerInterface interface { // (GET /ensure-everything-is-referenced) EnsureEverythingIsReferenced(ctx echo.Context) error + // (GET /issues/1051) + Issue1051(ctx echo.Context) error + // (GET /issues/127) Issue127(ctx echo.Context) error @@ -1146,6 +1404,9 @@ type ServerInterface interface { // (GET /issues/9) Issue9(ctx echo.Context, params Issue9Params) error + + // (GET /issues/975) + Issue975(ctx echo.Context) error } // ServerInterfaceWrapper converts echo contexts to parameters. @@ -1157,20 +1418,31 @@ type ServerInterfaceWrapper struct { func (w *ServerInterfaceWrapper) EnsureEverythingIsReferenced(ctx echo.Context) error { var err error - ctx.Set(Access_tokenScopes, []string{""}) + ctx.Set(Access_tokenScopes, []string{}) - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.EnsureEverythingIsReferenced(ctx) return err } +// Issue1051 converts echo context to params. +func (w *ServerInterfaceWrapper) Issue1051(ctx echo.Context) error { + var err error + + ctx.Set(Access_tokenScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.Issue1051(ctx) + return err +} + // Issue127 converts echo context to params. func (w *ServerInterfaceWrapper) Issue127(ctx echo.Context) error { var err error - ctx.Set(Access_tokenScopes, []string{""}) + ctx.Set(Access_tokenScopes, []string{}) - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.Issue127(ctx) return err } @@ -1179,9 +1451,9 @@ func (w *ServerInterfaceWrapper) Issue127(ctx echo.Context) error { func (w *ServerInterfaceWrapper) Issue185(ctx echo.Context) error { var err error - ctx.Set(Access_tokenScopes, []string{""}) + ctx.Set(Access_tokenScopes, []string{}) - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.Issue185(ctx) return err } @@ -1192,14 +1464,14 @@ func (w *ServerInterfaceWrapper) Issue209(ctx echo.Context) error { // ------------- Path parameter "str" ------------- var str StringInPath - err = runtime.BindStyledParameterWithLocation("simple", false, "str", runtime.ParamLocationPath, ctx.Param("str"), &str) + err = runtime.BindStyledParameterWithOptions("simple", "str", ctx.Param("str"), &str, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter str: %s", err)) } - ctx.Set(Access_tokenScopes, []string{""}) + ctx.Set(Access_tokenScopes, []string{}) - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.Issue209(ctx, str) return err } @@ -1210,14 +1482,14 @@ func (w *ServerInterfaceWrapper) Issue30(ctx echo.Context) error { // ------------- Path parameter "fallthrough" ------------- var pFallthrough string - err = runtime.BindStyledParameterWithLocation("simple", false, "fallthrough", runtime.ParamLocationPath, ctx.Param("fallthrough"), &pFallthrough) + err = runtime.BindStyledParameterWithOptions("simple", "fallthrough", ctx.Param("fallthrough"), &pFallthrough, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter fallthrough: %s", err)) } - ctx.Set(Access_tokenScopes, []string{""}) + ctx.Set(Access_tokenScopes, []string{}) - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.Issue30(ctx, pFallthrough) return err } @@ -1226,9 +1498,9 @@ func (w *ServerInterfaceWrapper) Issue30(ctx echo.Context) error { func (w *ServerInterfaceWrapper) GetIssues375(ctx echo.Context) error { var err error - ctx.Set(Access_tokenScopes, []string{""}) + ctx.Set(Access_tokenScopes, []string{}) - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetIssues375(ctx) return err } @@ -1239,14 +1511,14 @@ func (w *ServerInterfaceWrapper) Issue41(ctx echo.Context) error { // ------------- Path parameter "1param" ------------- var n1param N5StartsWithNumber - err = runtime.BindStyledParameterWithLocation("simple", false, "1param", runtime.ParamLocationPath, ctx.Param("1param"), &n1param) + err = runtime.BindStyledParameterWithOptions("simple", "1param", ctx.Param("1param"), &n1param, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "object", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter 1param: %s", err)) } - ctx.Set(Access_tokenScopes, []string{""}) + ctx.Set(Access_tokenScopes, []string{}) - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.Issue41(ctx, n1param) return err } @@ -1255,22 +1527,33 @@ func (w *ServerInterfaceWrapper) Issue41(ctx echo.Context) error { func (w *ServerInterfaceWrapper) Issue9(ctx echo.Context) error { var err error - ctx.Set(Access_tokenScopes, []string{""}) + ctx.Set(Access_tokenScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params Issue9Params // ------------- Required query parameter "foo" ------------- - err = runtime.BindQueryParameter("form", true, true, "foo", ctx.QueryParams(), ¶ms.Foo) + err = runtime.BindQueryParameterWithOptions("form", true, true, "foo", ctx.QueryParams(), ¶ms.Foo, runtime.BindQueryParameterOptions{Type: "string", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter foo: %s", err)) } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.Issue9(ctx, params) return err } +// Issue975 converts echo context to params. +func (w *ServerInterfaceWrapper) Issue975(ctx echo.Context) error { + var err error + + ctx.Set(Access_tokenScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.Issue975(ctx) + return err +} + // This is a simple interface which specifies echo.Route addition functions which // are present on both echo.Echo and echo.Group, since we want to allow using // either of them for path registration @@ -1300,6 +1583,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL } router.GET(baseURL+"/ensure-everything-is-referenced", wrapper.EnsureEverythingIsReferenced) + router.GET(baseURL+"/issues/1051", wrapper.Issue1051) router.GET(baseURL+"/issues/127", wrapper.Issue127) router.GET(baseURL+"/issues/185", wrapper.Issue185) router.GET(baseURL+"/issues/209/$:str", wrapper.Issue209) @@ -1307,33 +1591,39 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/issues/375", wrapper.GetIssues375) router.GET(baseURL+"/issues/41/:1param", wrapper.Issue41) router.GET(baseURL+"/issues/9", wrapper.Issue9) + router.GET(baseURL+"/issues/975", wrapper.Issue975) } // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/7RX0W/buA/+Vwj9BvxenDjNNmzNW2/YDT3gtmItsIemD4rNxFptypPotkaQ//1AyamT", - "xelt160vjS1R5PeR/ESvVWar2hISezVbq1o7XSGjC0+X7AytzulCcyHPOfrMmZqNJTVTZ+DDOtSaC3i0", - "VIkysixvVaJIV6hmyrMsOPzWGIe5mrFrMFE+K7DScjS3dbfN0EptNpvtYgjk9SVrx/6L4eJjUy3QHUZz", - "VRgP0QTEJ/hgAveGC9BA0SzZOrKLr5ix2iTqjNqrtsYTNVv3T9MBuN0KOKwdemEMNLUgB47nNKcYQWGb", - "MocFgiYwxOiWOsP1Zk7i613j2VaR1qsQyFotras0q5nKwmIfYsdFoh5GVtdmlNkcV0gjfGCnR6xXPppb", - "NVML7ZRw9p6a6pw+Lb6e05lzupUdhrGKyXW2RscGw9OdLuUfUlOp2bVaGudZJcpjZilXN8lBSga4617o", - "4GqTqA9I6Ez2KW7o09pbfGzKUi9KvNiLZT8yGyiP4X0XRPK4eEb59izZR4+/Y2Ud2PWltz6++HOH7p16", - "3f8ePu/mgL9NYLtxhttLKdyIXmcZej9ie4skzwvUDt2f2yr568vVKJYMxJ0Qdo7npLqWERfRqK+lgrmO", - "XWVoaQe6Bz1Dpj16WFoHd9oZ23gw3jfhVUM52Dt0wKbCMVyUqD2CznPQwFtbMZ2T9MSiWcHSPGAew2LD", - "QmL0conuLoR2h85H7yfjyXgSk4uka6Nm6uV4Mj5RSVCRQEuK5BuHI7xD13JhaDUyfuRwiQ4pi3ldIR8R", - "BqS8toYY8MF49uAtcKEZevWDTJO0beZQM+ZgCLgwfk6+xgw05UCWZUPtGsI84JKi1eLmPFcz9T4E+P4x", - "vnP/uY9OasLXlnxM8nQykX+ZJUYKQeu6Lk0WTku/ehtS38vjfoPoXrLUC4dLNVP/S3soaaec6aO0bZKt", - "zfQHbaZikw3I1VO2B/I2IBrxL1FprK30ZPrmaOr+1rcIQio05Ju6tk4yE0h74CC8HnJL/2eoHWJVM/S7", - "wup4IE3n4le8PjMlTxGxr4MCd/esh6p8zlECPq20u83tPT37oFY/Jxo5Jselbkr+jeT9IsTfV97b18dF", - "o60RVmIfEMB9gQTbqyfdyjv0bQnaIWzvi+Nl9/Z1dzug5z9s3v4y0gbu1Yh2p8YlvF0CppPT9MXas9sc", - "5eFdgdmtB7Ps57sINces1D0FZTsMeDo5VYcxJHtz5vUwsn5LujeHbm52ILycpOulLksunG1WxeYQwWf0", - "cuHkcIvtvXX57ohWOwy3lIi9XHlCYBgeO+HoKBnA9XLyI7AG5uCdYH9qHt4D/eZ44coA2CWnq1ztt4Us", - "qnhvMpR0coEgo19YNyTTalToOd0XJiu6997kCHYpy2HIG6rsD8iBEy9x/UZRPZhtDzr61Um6Pgk5OF7R", - "F9sU7XwlyEdM+E54/EoYSPmrOI78W4Kj/ydz+xTIwy+dzebmyS4+Pd68pUHi2Lk+XIhgKLPOYcZlK7/L", - "Jsc8THydJkUaFjZvZeSZU4/3qKadHqHlW4Ou3Sl8a3+u4P+zTnaX0i4TnzrlDsjUkCruzOIBwv4Ufn0j", - "8QQh6SA2ruzG6lmadmOrDMLjHLGudD3WRpTqnwAAAP//yJQBrWAPAAA=", + "H4sIAAAAAAAC/7RYXW/buBL9Kyxvgftw/Z0GbfyW2+0WLrBJ0GTRhzoPtDi22EhDlaTsCIb++2JI2bIj", + "yW02bV5iiZzhmTPDM6S2PNJpphHQWT7d8kwYkYID459unVG4muGNcDE9S7CRUZlTGvmUXzLrx1kmXMz2", + "lrzHFQ3TW97jKFLgU24dDRj4nisDkk+dyaHHbRRDKsi1K7JqmsIVL8tyN+iBnN86YZz9olx8lacLME00", + "d7GyLJgwWpNZb8I2ysVMMAxmvd1CevENIsfLHr/E4q7IYMyn2/pp0hJuNcIMZAYsMcYEFowcDuY4x4Ag", + "1nki2QKYQKbQgVmKCLblHGmt97l1Og203nkgW77UJhWOT3nkB2uIFRc9/tjXIlP9SEtYAfbh0RnRd2Jl", + "g7nmU74QhhNnfxC2SDiQN0ZnYFzhsxp+K/AWCBsabEb4twXmKAjUm1cNHGWP68S7HQfT3Uq7ZHZNn7RP", + "P1575lhuQTKnmdQBhUDJXCzcCSRnP4OECKzn9A0Iuw/3KnDBFFoHQr468P3mV8N+Ho7ycLd83SdtD4/f", + "t9TyB8zTGV4vvs3w0hjhk68cpLZZBWuR0D/APCX/S2UsQbYQaZQHzvc7smW56oXwS5U9/hEQjIquw4R6", + "V9cWV3mSiEUCN0dYjpFpT26A10x8NXiJcufL1/T+d0ct1lxuuwef5/RJhva/2/21pes6d2BIB0jYLlFj", + "kerczhCDwB3TojpeH4ZEgrMC08CmZHN9KseV7tPLfiXSfuXPQE9yD+d6DzfM2v6ABz+rV8FtRl36GsuN", + "csUtqXWIQkQRWNt3+gGQnhcgDJg/d9L46ctdP+gkCzOZnzmYI6/6BC0RjOp9FzuXhVaicKlbWgZYxyJh", + "wbKlNmwtjNK5Zcra3L/KUTK9BsOcSmHAbhIQFpiQkgnmdrZkOkdqBIt8xZbqEWSA5ZSj0gmr3IJZe2hr", + "MDasPh6MBqNQ0oAiU3zKzwajwZj3fOv0tAwBbW6gD2swhYsVrvrK9g0swQBGoZpX4Dq6IaDMtELH4FFZ", + "Z5nVXphY3fJZJJB6VWSANIkp9Bo2R5tB5JUMtaMJmckRpI+Lik/QMjPJp/yDB/hhj29mP9foqDBsptGG", + "JE9GI/oXaXSAHrTIskRF3tvwm1fD7cGZ4LjQRd2n+WsDSz7l/xnWoQyr48Jw38/L3s5m8pM2E7KJWnr0", + "KdtGT2+RyvDX48NQW8Px6Hzcmbu/8sSpLAGWglTCny8sI9KEQvbp9vqqJQ0z8uu9vpDz5m49nL9GObA6", + "BZ/qwXr8vx87eBr55G134OIBGJUTy9HmWaYN1aSH/ugqHqTG/zqWGYA0c6ye5UcHncxM3r6UmFMlcNz3", + "npL2mCYvcUXBD1NhHqTe4IsdFeIlaMiNhKXIE/cbyftFET+tvHfn3XJZZMBWZO8jYJsYkO2OGsNdd2O1", + "IDFhgO3OB91l9+68Og2Adf/XsvhlpLWco0K0BzVO8A4JmIwuhq+31pmyk4f3MUQPlqllfZ0LoUqIElFT", + "kBTtAU9GF7yJoXd0rfzaHlk9ZXh07SzvD0I4Gw23S5EkLjY6X8VlM4LPYKnVSvYAxUYbeXgjywz4/kxt", + "jpo9EejvipVwVJS0xHU2+pmwWq69B2Cfdf09Cvptd+HSgb9KTlW5wu4KmVRxoyKgdLoYGB31/bhCupwG", + "hZ7jJlZRXL23SgLTSxr2h/q2yv4IznNiCddvFNXGXaaxo9+Mh9uxz0F3Rd/sUnTwUUDhKnwW2H8UaEn5", + "m3AQ+1GCw/onc3sqyOaHjbK8P7mLL7o3b6IAXdi51jdEpjDSxkDkkoJ+J7kE6c+6lSYFGhZaFnTYm2Md", + "b6emXXTQ8j0HUxwUvtbPK/h/rZNVUzpk4rpSbh8ZP62KFyd2V/01hS0VJLWYrMAxUWkhHadTQNdJ2O/d", + "Ji1ffFoY8d/q8qhKuGzE5V+vhSlobySwhoRkQOoop9A8Ll7tvt3tzaf++N729Z7y6AW4Ko3cJNVFbDoc", + "VhcdujoNJECWimwgFCn8PwEAAP//WUiKvIcUAAA=", } // GetSwagger returns the content of the embedded swagger specification file @@ -1341,16 +1631,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -1368,7 +1658,7 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } @@ -1382,12 +1672,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/internal/test/schemas/schemas.yaml b/internal/test/schemas/schemas.yaml index 3d73527c60..2e094de126 100644 --- a/internal/test/schemas/schemas.yaml +++ b/internal/test/schemas/schemas.yaml @@ -121,6 +121,32 @@ paths: application/json: schema: $ref: "#/components/schemas/EnumInObjInArray" + /issues/975: + get: + operationId: Issue975 + description: | + Deprecated fields should get a proper comment + responses: + 200: + description: A struct with deprecated fields with varying level of documentation + content: + application/json: + schema: + $ref: "#/components/schemas/DeprecatedProperty" + /issues/1051: + get: + operationId: Issue1051 + description: | + Multiple media types contain JSON + responses: + '200': + content: + application/vnd.something.v1+json: + schema: + type: object + application/json: + schema: + type: object components: schemas: GenericObject: @@ -165,6 +191,45 @@ components: enum: - first - second + DeprecatedProperty: + type: object + required: + - newProp + - oldProp + properties: + newProp: + type: string + description: Use this now! + oldProp1: + type: string + deprecated: true + # description: No description on this one to test generation in that case + oldProp2: + type: string + deprecated: true + description: It used to do this and that + oldProp3: + type: string + deprecated: true + x-deprecated-reason: Use NewProp instead! + oldProp4: + type: string + deprecated: true + x-deprecated-reason: Use NewProp instead! + description: It used to do this and that + OuterTypeWithAnonymousInner: + type: object + properties: + name: + type: string + inner: + type: object + x-go-type-name: InnerRenamedAnonymousObject + properties: + id: + type: integer + required: [ id ] + required: [ name, inner ] parameters: StringInPath: name: str @@ -183,4 +248,3 @@ components: JWT-format access token. security: - access-token: [ ] - diff --git a/internal/test/server/config.yaml b/internal/test/server/config.yaml index 729401be70..4e8702b1f3 100644 --- a/internal/test/server/config.yaml +++ b/internal/test/server/config.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../configuration-schema.json package: server generate: chi-server: true diff --git a/internal/test/server/doc.go b/internal/test/server/doc.go index 1f7a617f9d..f46abed62f 100644 --- a/internal/test/server/doc.go +++ b/internal/test/server/doc.go @@ -1,8 +1,3 @@ package server -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml ../test-schema.yaml - -// This is commented out because the server_mog.gen.go keeps changing for no good reason, and -// so, precommit checks fail. We need to regenerate this file occasionally manually. -// TODO(mromaszewicz) - figure out why this file drifts and fix it. -// go:generate go run github.com/matryer/moq -out server_moq.gen.go . ServerInterface +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../test-schema.yaml diff --git a/internal/test/server/server.gen.go b/internal/test/server/server.gen.go index d43de70d1a..67e89ff318 100644 --- a/internal/test/server/server.gen.go +++ b/internal/test/server/server.gen.go @@ -1,6 +1,6 @@ // Package server provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package server import ( @@ -8,9 +8,9 @@ import ( "net/http" "time" - "github.com/deepmap/oapi-codegen/pkg/runtime" - openapi_types "github.com/deepmap/oapi-codegen/pkg/types" "github.com/go-chi/chi/v5" + "github.com/oapi-codegen/runtime" + openapi_types "github.com/oapi-codegen/runtime/types" ) // Defines values for GetWithContentTypeParamsContentType. @@ -19,6 +19,18 @@ const ( Text GetWithContentTypeParamsContentType = "text" ) +// Valid indicates whether the value is a known member of the GetWithContentTypeParamsContentType enum. +func (e GetWithContentTypeParamsContentType) Valid() bool { + switch e { + case Json: + return true + case Text: + return true + default: + return false + } +} + // EveryTypeOptional defines model for EveryTypeOptional. type EveryTypeOptional struct { ArrayInlineField *[]int `json:"array_inline_field,omitempty"` @@ -162,6 +174,71 @@ type ServerInterface interface { GetResponseWithReference(w http.ResponseWriter, r *http.Request) } +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct{} + +// get every type optional +// (GET /every-type-optional) +func (_ Unimplemented) GetEveryTypeOptional(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Get resource via simple path +// (GET /get-simple) +func (_ Unimplemented) GetSimple(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Getter with referenced parameter and referenced response +// (GET /get-with-args) +func (_ Unimplemented) GetWithArgs(w http.ResponseWriter, r *http.Request, params GetWithArgsParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Getter with referenced parameter and referenced response +// (GET /get-with-references/{global_argument}/{argument}) +func (_ Unimplemented) GetWithReferences(w http.ResponseWriter, r *http.Request, globalArgument int64, argument Argument) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Get an object by ID +// (GET /get-with-type/{content_type}) +func (_ Unimplemented) GetWithContentType(w http.ResponseWriter, r *http.Request, contentType GetWithContentTypeParamsContentType) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get with reserved keyword +// (GET /reserved-keyword) +func (_ Unimplemented) GetReservedKeyword(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Create a resource +// (POST /resource/{argument}) +func (_ Unimplemented) CreateResource(w http.ResponseWriter, r *http.Request, argument Argument) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Create a resource with inline parameter +// (POST /resource2/{inline_argument}) +func (_ Unimplemented) CreateResource2(w http.ResponseWriter, r *http.Request, inlineArgument int, params CreateResource2Params) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Update a resource with inline body. The parameter name is a reserved +// keyword, so make sure that gets prefixed to avoid syntax errors +// (PUT /resource3/{fallthrough}) +func (_ Unimplemented) UpdateResource3(w http.ResponseWriter, r *http.Request, pFallthrough int) { + w.WriteHeader(http.StatusNotImplemented) +} + +// get response with reference +// (GET /response-with-reference) +func (_ Unimplemented) GetResponseWithReference(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + // ServerInterfaceWrapper converts contexts to parameters. type ServerInterfaceWrapper struct { Handler ServerInterface @@ -173,37 +250,34 @@ type MiddlewareFunc func(http.Handler) http.Handler // GetEveryTypeOptional operation middleware func (siw *ServerInterfaceWrapper) GetEveryTypeOptional(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetEveryTypeOptional(w, r) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // GetSimple operation middleware func (siw *ServerInterfaceWrapper) GetSimple(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetSimple(w, r) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // GetWithArgs operation middleware func (siw *ServerInterfaceWrapper) GetWithArgs(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() var err error @@ -212,7 +286,7 @@ func (siw *ServerInterfaceWrapper) GetWithArgs(w http.ResponseWriter, r *http.Re // ------------- Optional query parameter "optional_argument" ------------- - err = runtime.BindQueryParameter("form", true, false, "optional_argument", r.URL.Query(), ¶ms.OptionalArgument) + err = runtime.BindQueryParameterWithOptions("form", true, false, "optional_argument", r.URL.Query(), ¶ms.OptionalArgument, runtime.BindQueryParameterOptions{Type: "integer", Format: "int64"}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "optional_argument", Err: err}) return @@ -227,7 +301,7 @@ func (siw *ServerInterfaceWrapper) GetWithArgs(w http.ResponseWriter, r *http.Re return } - err = runtime.BindQueryParameter("form", true, true, "required_argument", r.URL.Query(), ¶ms.RequiredArgument) + err = runtime.BindQueryParameterWithOptions("form", true, true, "required_argument", r.URL.Query(), ¶ms.RequiredArgument, runtime.BindQueryParameterOptions{Type: "integer", Format: "int64"}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "required_argument", Err: err}) return @@ -244,7 +318,7 @@ func (siw *ServerInterfaceWrapper) GetWithArgs(w http.ResponseWriter, r *http.Re return } - err = runtime.BindStyledParameterWithLocation("simple", false, "header_argument", runtime.ParamLocationHeader, valueList[0], &HeaderArgument) + err = runtime.BindStyledParameterWithOptions("simple", "header_argument", valueList[0], &HeaderArgument, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false, Type: "integer", Format: "int32"}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "header_argument", Err: err}) return @@ -254,27 +328,26 @@ func (siw *ServerInterfaceWrapper) GetWithArgs(w http.ResponseWriter, r *http.Re } - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetWithArgs(w, r, params) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // GetWithReferences operation middleware func (siw *ServerInterfaceWrapper) GetWithReferences(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() var err error // ------------- Path parameter "global_argument" ------------- var globalArgument int64 - err = runtime.BindStyledParameterWithLocation("simple", false, "global_argument", runtime.ParamLocationPath, chi.URLParam(r, "global_argument"), &globalArgument) + err = runtime.BindStyledParameterWithOptions("simple", "global_argument", chi.URLParam(r, "global_argument"), &globalArgument, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "integer", Format: "int64"}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "global_argument", Err: err}) return @@ -283,100 +356,96 @@ func (siw *ServerInterfaceWrapper) GetWithReferences(w http.ResponseWriter, r *h // ------------- Path parameter "argument" ------------- var argument Argument - err = runtime.BindStyledParameterWithLocation("simple", false, "argument", runtime.ParamLocationPath, chi.URLParam(r, "argument"), &argument) + err = runtime.BindStyledParameterWithOptions("simple", "argument", chi.URLParam(r, "argument"), &argument, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "argument", Err: err}) return } - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetWithReferences(w, r, globalArgument, argument) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // GetWithContentType operation middleware func (siw *ServerInterfaceWrapper) GetWithContentType(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() var err error // ------------- Path parameter "content_type" ------------- var contentType GetWithContentTypeParamsContentType - err = runtime.BindStyledParameterWithLocation("simple", false, "content_type", runtime.ParamLocationPath, chi.URLParam(r, "content_type"), &contentType) + err = runtime.BindStyledParameterWithOptions("simple", "content_type", chi.URLParam(r, "content_type"), &contentType, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "content_type", Err: err}) return } - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetWithContentType(w, r, contentType) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // GetReservedKeyword operation middleware func (siw *ServerInterfaceWrapper) GetReservedKeyword(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetReservedKeyword(w, r) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // CreateResource operation middleware func (siw *ServerInterfaceWrapper) CreateResource(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() var err error // ------------- Path parameter "argument" ------------- var argument Argument - err = runtime.BindStyledParameterWithLocation("simple", false, "argument", runtime.ParamLocationPath, chi.URLParam(r, "argument"), &argument) + err = runtime.BindStyledParameterWithOptions("simple", "argument", chi.URLParam(r, "argument"), &argument, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "argument", Err: err}) return } - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.CreateResource(w, r, argument) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // CreateResource2 operation middleware func (siw *ServerInterfaceWrapper) CreateResource2(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() var err error // ------------- Path parameter "inline_argument" ------------- var inlineArgument int - err = runtime.BindStyledParameterWithLocation("simple", false, "inline_argument", runtime.ParamLocationPath, chi.URLParam(r, "inline_argument"), &inlineArgument) + err = runtime.BindStyledParameterWithOptions("simple", "inline_argument", chi.URLParam(r, "inline_argument"), &inlineArgument, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "integer", Format: ""}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "inline_argument", Err: err}) return @@ -387,62 +456,60 @@ func (siw *ServerInterfaceWrapper) CreateResource2(w http.ResponseWriter, r *htt // ------------- Optional query parameter "inline_query_argument" ------------- - err = runtime.BindQueryParameter("form", true, false, "inline_query_argument", r.URL.Query(), ¶ms.InlineQueryArgument) + err = runtime.BindQueryParameterWithOptions("form", true, false, "inline_query_argument", r.URL.Query(), ¶ms.InlineQueryArgument, runtime.BindQueryParameterOptions{Type: "integer", Format: ""}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "inline_query_argument", Err: err}) return } - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.CreateResource2(w, r, inlineArgument, params) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // UpdateResource3 operation middleware func (siw *ServerInterfaceWrapper) UpdateResource3(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() var err error // ------------- Path parameter "fallthrough" ------------- var pFallthrough int - err = runtime.BindStyledParameterWithLocation("simple", false, "fallthrough", runtime.ParamLocationPath, chi.URLParam(r, "fallthrough"), &pFallthrough) + err = runtime.BindStyledParameterWithOptions("simple", "fallthrough", chi.URLParam(r, "fallthrough"), &pFallthrough, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "integer", Format: ""}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "fallthrough", Err: err}) return } - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.UpdateResource3(w, r, pFallthrough) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // GetResponseWithReference operation middleware func (siw *ServerInterfaceWrapper) GetResponseWithReference(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetResponseWithReference(w, r) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } type UnescapedCookieParamError struct { @@ -458,16 +525,16 @@ func (e *UnescapedCookieParamError) Unwrap() error { return e.Err } -type UnmarshallingParamError struct { +type UnmarshalingParamError struct { ParamName string Err error } -func (e *UnmarshallingParamError) Error() string { - return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) } -func (e *UnmarshallingParamError) Unwrap() error { +func (e *UnmarshalingParamError) Unwrap() error { return e.Err } diff --git a/internal/test/server/server_moq.gen.go b/internal/test/server/server_moq.gen.go deleted file mode 100644 index 80bc73a9e0..0000000000 --- a/internal/test/server/server_moq.gen.go +++ /dev/null @@ -1,579 +0,0 @@ -// Code generated by moq; DO NOT EDIT. -// github.com/matryer/moq - -package server - -import ( - "net/http" - "sync" -) - -// Ensure, that ServerInterfaceMock does implement ServerInterface. -// If this is not the case, regenerate this file with moq. -var _ ServerInterface = &ServerInterfaceMock{} - -// ServerInterfaceMock is a mock implementation of ServerInterface. -// -// func TestSomethingThatUsesServerInterface(t *testing.T) { -// -// // make and configure a mocked ServerInterface -// mockedServerInterface := &ServerInterfaceMock{ -// CreateResourceFunc: func(w http.ResponseWriter, r *http.Request, argument string) { -// panic("mock out the CreateResource method") -// }, -// CreateResource2Func: func(w http.ResponseWriter, r *http.Request, inlineArgument int, params CreateResource2Params) { -// panic("mock out the CreateResource2 method") -// }, -// GetEveryTypeOptionalFunc: func(w http.ResponseWriter, r *http.Request) { -// panic("mock out the GetEveryTypeOptional method") -// }, -// GetReservedKeywordFunc: func(w http.ResponseWriter, r *http.Request) { -// panic("mock out the GetReservedKeyword method") -// }, -// GetResponseWithReferenceFunc: func(w http.ResponseWriter, r *http.Request) { -// panic("mock out the GetResponseWithReference method") -// }, -// GetSimpleFunc: func(w http.ResponseWriter, r *http.Request) { -// panic("mock out the GetSimple method") -// }, -// GetWithArgsFunc: func(w http.ResponseWriter, r *http.Request, params GetWithArgsParams) { -// panic("mock out the GetWithArgs method") -// }, -// GetWithContentTypeFunc: func(w http.ResponseWriter, r *http.Request, contentType GetWithContentTypeParamsContentType) { -// panic("mock out the GetWithContentType method") -// }, -// GetWithReferencesFunc: func(w http.ResponseWriter, r *http.Request, globalArgument int64, argument string) { -// panic("mock out the GetWithReferences method") -// }, -// UpdateResource3Func: func(w http.ResponseWriter, r *http.Request, pFallthrough int) { -// panic("mock out the UpdateResource3 method") -// }, -// } -// -// // use mockedServerInterface in code that requires ServerInterface -// // and then make assertions. -// -// } -type ServerInterfaceMock struct { - // CreateResourceFunc mocks the CreateResource method. - CreateResourceFunc func(w http.ResponseWriter, r *http.Request, argument string) - - // CreateResource2Func mocks the CreateResource2 method. - CreateResource2Func func(w http.ResponseWriter, r *http.Request, inlineArgument int, params CreateResource2Params) - - // GetEveryTypeOptionalFunc mocks the GetEveryTypeOptional method. - GetEveryTypeOptionalFunc func(w http.ResponseWriter, r *http.Request) - - // GetReservedKeywordFunc mocks the GetReservedKeyword method. - GetReservedKeywordFunc func(w http.ResponseWriter, r *http.Request) - - // GetResponseWithReferenceFunc mocks the GetResponseWithReference method. - GetResponseWithReferenceFunc func(w http.ResponseWriter, r *http.Request) - - // GetSimpleFunc mocks the GetSimple method. - GetSimpleFunc func(w http.ResponseWriter, r *http.Request) - - // GetWithArgsFunc mocks the GetWithArgs method. - GetWithArgsFunc func(w http.ResponseWriter, r *http.Request, params GetWithArgsParams) - - // GetWithContentTypeFunc mocks the GetWithContentType method. - GetWithContentTypeFunc func(w http.ResponseWriter, r *http.Request, contentType GetWithContentTypeParamsContentType) - - // GetWithReferencesFunc mocks the GetWithReferences method. - GetWithReferencesFunc func(w http.ResponseWriter, r *http.Request, globalArgument int64, argument string) - - // UpdateResource3Func mocks the UpdateResource3 method. - UpdateResource3Func func(w http.ResponseWriter, r *http.Request, pFallthrough int) - - // calls tracks calls to the methods. - calls struct { - // CreateResource holds details about calls to the CreateResource method. - CreateResource []struct { - // W is the w argument value. - W http.ResponseWriter - // R is the r argument value. - R *http.Request - // Argument is the argument argument value. - Argument string - } - // CreateResource2 holds details about calls to the CreateResource2 method. - CreateResource2 []struct { - // W is the w argument value. - W http.ResponseWriter - // R is the r argument value. - R *http.Request - // InlineArgument is the inlineArgument argument value. - InlineArgument int - // Params is the params argument value. - Params CreateResource2Params - } - // GetEveryTypeOptional holds details about calls to the GetEveryTypeOptional method. - GetEveryTypeOptional []struct { - // W is the w argument value. - W http.ResponseWriter - // R is the r argument value. - R *http.Request - } - // GetReservedKeyword holds details about calls to the GetReservedKeyword method. - GetReservedKeyword []struct { - // W is the w argument value. - W http.ResponseWriter - // R is the r argument value. - R *http.Request - } - // GetResponseWithReference holds details about calls to the GetResponseWithReference method. - GetResponseWithReference []struct { - // W is the w argument value. - W http.ResponseWriter - // R is the r argument value. - R *http.Request - } - // GetSimple holds details about calls to the GetSimple method. - GetSimple []struct { - // W is the w argument value. - W http.ResponseWriter - // R is the r argument value. - R *http.Request - } - // GetWithArgs holds details about calls to the GetWithArgs method. - GetWithArgs []struct { - // W is the w argument value. - W http.ResponseWriter - // R is the r argument value. - R *http.Request - // Params is the params argument value. - Params GetWithArgsParams - } - // GetWithContentType holds details about calls to the GetWithContentType method. - GetWithContentType []struct { - // W is the w argument value. - W http.ResponseWriter - // R is the r argument value. - R *http.Request - // ContentType is the contentType argument value. - ContentType GetWithContentTypeParamsContentType - } - // GetWithReferences holds details about calls to the GetWithReferences method. - GetWithReferences []struct { - // W is the w argument value. - W http.ResponseWriter - // R is the r argument value. - R *http.Request - // GlobalArgument is the globalArgument argument value. - GlobalArgument int64 - // Argument is the argument argument value. - Argument string - } - // UpdateResource3 holds details about calls to the UpdateResource3 method. - UpdateResource3 []struct { - // W is the w argument value. - W http.ResponseWriter - // R is the r argument value. - R *http.Request - // PFallthrough is the pFallthrough argument value. - PFallthrough int - } - } - lockCreateResource sync.RWMutex - lockCreateResource2 sync.RWMutex - lockGetEveryTypeOptional sync.RWMutex - lockGetReservedKeyword sync.RWMutex - lockGetResponseWithReference sync.RWMutex - lockGetSimple sync.RWMutex - lockGetWithArgs sync.RWMutex - lockGetWithContentType sync.RWMutex - lockGetWithReferences sync.RWMutex - lockUpdateResource3 sync.RWMutex -} - -// CreateResource calls CreateResourceFunc. -func (mock *ServerInterfaceMock) CreateResource(w http.ResponseWriter, r *http.Request, argument string) { - if mock.CreateResourceFunc == nil { - panic("ServerInterfaceMock.CreateResourceFunc: method is nil but ServerInterface.CreateResource was just called") - } - callInfo := struct { - W http.ResponseWriter - R *http.Request - Argument string - }{ - W: w, - R: r, - Argument: argument, - } - mock.lockCreateResource.Lock() - mock.calls.CreateResource = append(mock.calls.CreateResource, callInfo) - mock.lockCreateResource.Unlock() - mock.CreateResourceFunc(w, r, argument) -} - -// CreateResourceCalls gets all the calls that were made to CreateResource. -// Check the length with: -// -// len(mockedServerInterface.CreateResourceCalls()) -func (mock *ServerInterfaceMock) CreateResourceCalls() []struct { - W http.ResponseWriter - R *http.Request - Argument string -} { - var calls []struct { - W http.ResponseWriter - R *http.Request - Argument string - } - mock.lockCreateResource.RLock() - calls = mock.calls.CreateResource - mock.lockCreateResource.RUnlock() - return calls -} - -// CreateResource2 calls CreateResource2Func. -func (mock *ServerInterfaceMock) CreateResource2(w http.ResponseWriter, r *http.Request, inlineArgument int, params CreateResource2Params) { - if mock.CreateResource2Func == nil { - panic("ServerInterfaceMock.CreateResource2Func: method is nil but ServerInterface.CreateResource2 was just called") - } - callInfo := struct { - W http.ResponseWriter - R *http.Request - InlineArgument int - Params CreateResource2Params - }{ - W: w, - R: r, - InlineArgument: inlineArgument, - Params: params, - } - mock.lockCreateResource2.Lock() - mock.calls.CreateResource2 = append(mock.calls.CreateResource2, callInfo) - mock.lockCreateResource2.Unlock() - mock.CreateResource2Func(w, r, inlineArgument, params) -} - -// CreateResource2Calls gets all the calls that were made to CreateResource2. -// Check the length with: -// -// len(mockedServerInterface.CreateResource2Calls()) -func (mock *ServerInterfaceMock) CreateResource2Calls() []struct { - W http.ResponseWriter - R *http.Request - InlineArgument int - Params CreateResource2Params -} { - var calls []struct { - W http.ResponseWriter - R *http.Request - InlineArgument int - Params CreateResource2Params - } - mock.lockCreateResource2.RLock() - calls = mock.calls.CreateResource2 - mock.lockCreateResource2.RUnlock() - return calls -} - -// GetEveryTypeOptional calls GetEveryTypeOptionalFunc. -func (mock *ServerInterfaceMock) GetEveryTypeOptional(w http.ResponseWriter, r *http.Request) { - if mock.GetEveryTypeOptionalFunc == nil { - panic("ServerInterfaceMock.GetEveryTypeOptionalFunc: method is nil but ServerInterface.GetEveryTypeOptional was just called") - } - callInfo := struct { - W http.ResponseWriter - R *http.Request - }{ - W: w, - R: r, - } - mock.lockGetEveryTypeOptional.Lock() - mock.calls.GetEveryTypeOptional = append(mock.calls.GetEveryTypeOptional, callInfo) - mock.lockGetEveryTypeOptional.Unlock() - mock.GetEveryTypeOptionalFunc(w, r) -} - -// GetEveryTypeOptionalCalls gets all the calls that were made to GetEveryTypeOptional. -// Check the length with: -// -// len(mockedServerInterface.GetEveryTypeOptionalCalls()) -func (mock *ServerInterfaceMock) GetEveryTypeOptionalCalls() []struct { - W http.ResponseWriter - R *http.Request -} { - var calls []struct { - W http.ResponseWriter - R *http.Request - } - mock.lockGetEveryTypeOptional.RLock() - calls = mock.calls.GetEveryTypeOptional - mock.lockGetEveryTypeOptional.RUnlock() - return calls -} - -// GetReservedKeyword calls GetReservedKeywordFunc. -func (mock *ServerInterfaceMock) GetReservedKeyword(w http.ResponseWriter, r *http.Request) { - if mock.GetReservedKeywordFunc == nil { - panic("ServerInterfaceMock.GetReservedKeywordFunc: method is nil but ServerInterface.GetReservedKeyword was just called") - } - callInfo := struct { - W http.ResponseWriter - R *http.Request - }{ - W: w, - R: r, - } - mock.lockGetReservedKeyword.Lock() - mock.calls.GetReservedKeyword = append(mock.calls.GetReservedKeyword, callInfo) - mock.lockGetReservedKeyword.Unlock() - mock.GetReservedKeywordFunc(w, r) -} - -// GetReservedKeywordCalls gets all the calls that were made to GetReservedKeyword. -// Check the length with: -// -// len(mockedServerInterface.GetReservedKeywordCalls()) -func (mock *ServerInterfaceMock) GetReservedKeywordCalls() []struct { - W http.ResponseWriter - R *http.Request -} { - var calls []struct { - W http.ResponseWriter - R *http.Request - } - mock.lockGetReservedKeyword.RLock() - calls = mock.calls.GetReservedKeyword - mock.lockGetReservedKeyword.RUnlock() - return calls -} - -// GetResponseWithReference calls GetResponseWithReferenceFunc. -func (mock *ServerInterfaceMock) GetResponseWithReference(w http.ResponseWriter, r *http.Request) { - if mock.GetResponseWithReferenceFunc == nil { - panic("ServerInterfaceMock.GetResponseWithReferenceFunc: method is nil but ServerInterface.GetResponseWithReference was just called") - } - callInfo := struct { - W http.ResponseWriter - R *http.Request - }{ - W: w, - R: r, - } - mock.lockGetResponseWithReference.Lock() - mock.calls.GetResponseWithReference = append(mock.calls.GetResponseWithReference, callInfo) - mock.lockGetResponseWithReference.Unlock() - mock.GetResponseWithReferenceFunc(w, r) -} - -// GetResponseWithReferenceCalls gets all the calls that were made to GetResponseWithReference. -// Check the length with: -// -// len(mockedServerInterface.GetResponseWithReferenceCalls()) -func (mock *ServerInterfaceMock) GetResponseWithReferenceCalls() []struct { - W http.ResponseWriter - R *http.Request -} { - var calls []struct { - W http.ResponseWriter - R *http.Request - } - mock.lockGetResponseWithReference.RLock() - calls = mock.calls.GetResponseWithReference - mock.lockGetResponseWithReference.RUnlock() - return calls -} - -// GetSimple calls GetSimpleFunc. -func (mock *ServerInterfaceMock) GetSimple(w http.ResponseWriter, r *http.Request) { - if mock.GetSimpleFunc == nil { - panic("ServerInterfaceMock.GetSimpleFunc: method is nil but ServerInterface.GetSimple was just called") - } - callInfo := struct { - W http.ResponseWriter - R *http.Request - }{ - W: w, - R: r, - } - mock.lockGetSimple.Lock() - mock.calls.GetSimple = append(mock.calls.GetSimple, callInfo) - mock.lockGetSimple.Unlock() - mock.GetSimpleFunc(w, r) -} - -// GetSimpleCalls gets all the calls that were made to GetSimple. -// Check the length with: -// -// len(mockedServerInterface.GetSimpleCalls()) -func (mock *ServerInterfaceMock) GetSimpleCalls() []struct { - W http.ResponseWriter - R *http.Request -} { - var calls []struct { - W http.ResponseWriter - R *http.Request - } - mock.lockGetSimple.RLock() - calls = mock.calls.GetSimple - mock.lockGetSimple.RUnlock() - return calls -} - -// GetWithArgs calls GetWithArgsFunc. -func (mock *ServerInterfaceMock) GetWithArgs(w http.ResponseWriter, r *http.Request, params GetWithArgsParams) { - if mock.GetWithArgsFunc == nil { - panic("ServerInterfaceMock.GetWithArgsFunc: method is nil but ServerInterface.GetWithArgs was just called") - } - callInfo := struct { - W http.ResponseWriter - R *http.Request - Params GetWithArgsParams - }{ - W: w, - R: r, - Params: params, - } - mock.lockGetWithArgs.Lock() - mock.calls.GetWithArgs = append(mock.calls.GetWithArgs, callInfo) - mock.lockGetWithArgs.Unlock() - mock.GetWithArgsFunc(w, r, params) -} - -// GetWithArgsCalls gets all the calls that were made to GetWithArgs. -// Check the length with: -// -// len(mockedServerInterface.GetWithArgsCalls()) -func (mock *ServerInterfaceMock) GetWithArgsCalls() []struct { - W http.ResponseWriter - R *http.Request - Params GetWithArgsParams -} { - var calls []struct { - W http.ResponseWriter - R *http.Request - Params GetWithArgsParams - } - mock.lockGetWithArgs.RLock() - calls = mock.calls.GetWithArgs - mock.lockGetWithArgs.RUnlock() - return calls -} - -// GetWithContentType calls GetWithContentTypeFunc. -func (mock *ServerInterfaceMock) GetWithContentType(w http.ResponseWriter, r *http.Request, contentType GetWithContentTypeParamsContentType) { - if mock.GetWithContentTypeFunc == nil { - panic("ServerInterfaceMock.GetWithContentTypeFunc: method is nil but ServerInterface.GetWithContentType was just called") - } - callInfo := struct { - W http.ResponseWriter - R *http.Request - ContentType GetWithContentTypeParamsContentType - }{ - W: w, - R: r, - ContentType: contentType, - } - mock.lockGetWithContentType.Lock() - mock.calls.GetWithContentType = append(mock.calls.GetWithContentType, callInfo) - mock.lockGetWithContentType.Unlock() - mock.GetWithContentTypeFunc(w, r, contentType) -} - -// GetWithContentTypeCalls gets all the calls that were made to GetWithContentType. -// Check the length with: -// -// len(mockedServerInterface.GetWithContentTypeCalls()) -func (mock *ServerInterfaceMock) GetWithContentTypeCalls() []struct { - W http.ResponseWriter - R *http.Request - ContentType GetWithContentTypeParamsContentType -} { - var calls []struct { - W http.ResponseWriter - R *http.Request - ContentType GetWithContentTypeParamsContentType - } - mock.lockGetWithContentType.RLock() - calls = mock.calls.GetWithContentType - mock.lockGetWithContentType.RUnlock() - return calls -} - -// GetWithReferences calls GetWithReferencesFunc. -func (mock *ServerInterfaceMock) GetWithReferences(w http.ResponseWriter, r *http.Request, globalArgument int64, argument string) { - if mock.GetWithReferencesFunc == nil { - panic("ServerInterfaceMock.GetWithReferencesFunc: method is nil but ServerInterface.GetWithReferences was just called") - } - callInfo := struct { - W http.ResponseWriter - R *http.Request - GlobalArgument int64 - Argument string - }{ - W: w, - R: r, - GlobalArgument: globalArgument, - Argument: argument, - } - mock.lockGetWithReferences.Lock() - mock.calls.GetWithReferences = append(mock.calls.GetWithReferences, callInfo) - mock.lockGetWithReferences.Unlock() - mock.GetWithReferencesFunc(w, r, globalArgument, argument) -} - -// GetWithReferencesCalls gets all the calls that were made to GetWithReferences. -// Check the length with: -// -// len(mockedServerInterface.GetWithReferencesCalls()) -func (mock *ServerInterfaceMock) GetWithReferencesCalls() []struct { - W http.ResponseWriter - R *http.Request - GlobalArgument int64 - Argument string -} { - var calls []struct { - W http.ResponseWriter - R *http.Request - GlobalArgument int64 - Argument string - } - mock.lockGetWithReferences.RLock() - calls = mock.calls.GetWithReferences - mock.lockGetWithReferences.RUnlock() - return calls -} - -// UpdateResource3 calls UpdateResource3Func. -func (mock *ServerInterfaceMock) UpdateResource3(w http.ResponseWriter, r *http.Request, pFallthrough int) { - if mock.UpdateResource3Func == nil { - panic("ServerInterfaceMock.UpdateResource3Func: method is nil but ServerInterface.UpdateResource3 was just called") - } - callInfo := struct { - W http.ResponseWriter - R *http.Request - PFallthrough int - }{ - W: w, - R: r, - PFallthrough: pFallthrough, - } - mock.lockUpdateResource3.Lock() - mock.calls.UpdateResource3 = append(mock.calls.UpdateResource3, callInfo) - mock.lockUpdateResource3.Unlock() - mock.UpdateResource3Func(w, r, pFallthrough) -} - -// UpdateResource3Calls gets all the calls that were made to UpdateResource3. -// Check the length with: -// -// len(mockedServerInterface.UpdateResource3Calls()) -func (mock *ServerInterfaceMock) UpdateResource3Calls() []struct { - W http.ResponseWriter - R *http.Request - PFallthrough int -} { - var calls []struct { - W http.ResponseWriter - R *http.Request - PFallthrough int - } - mock.lockUpdateResource3.RLock() - calls = mock.calls.UpdateResource3 - mock.lockUpdateResource3.RUnlock() - return calls -} diff --git a/internal/test/server/server_test.go b/internal/test/server/server_test.go index 43ee520921..386e3ddf5d 100644 --- a/internal/test/server/server_test.go +++ b/internal/test/server/server_test.go @@ -10,12 +10,86 @@ import ( "github.com/stretchr/testify/assert" ) -func TestParameters(t *testing.T) { - m := ServerInterfaceMock{} +type fakeServer struct { + t *testing.T + called bool +} + +// get every type optional +// (GET /every-type-optional) +func (s *fakeServer) GetEveryTypeOptional(w http.ResponseWriter, r *http.Request) { + // not implemented + w.WriteHeader(http.StatusTeapot) +} + +// Get resource via simple path +// (GET /get-simple) +func (s *fakeServer) GetSimple(w http.ResponseWriter, r *http.Request) { + // not implemented + w.WriteHeader(http.StatusTeapot) +} + +// Getter with referenced parameter and referenced response +// (GET /get-with-args) +func (s *fakeServer) GetWithArgs(w http.ResponseWriter, r *http.Request, params GetWithArgsParams) { + // not implemented + w.WriteHeader(http.StatusTeapot) +} + +// Getter with referenced parameter and referenced response +// (GET /get-with-references/{global_argument}/{argument}) +func (s *fakeServer) GetWithReferences(w http.ResponseWriter, r *http.Request, globalArgument int64, argument Argument) { + // not implemented + w.WriteHeader(http.StatusTeapot) +} + +// Get an object by ID +// (GET /get-with-type/{content_type}) +func (s *fakeServer) GetWithContentType(w http.ResponseWriter, r *http.Request, contentType GetWithContentTypeParamsContentType) { + // not implemented + w.WriteHeader(http.StatusTeapot) +} - m.CreateResource2Func = func(w http.ResponseWriter, r *http.Request, inlineArgument int, params CreateResource2Params) { - assert.Equal(t, 99, *params.InlineQueryArgument) - assert.Equal(t, 1, inlineArgument) +// get with reserved keyword +// (GET /reserved-keyword) +func (s *fakeServer) GetReservedKeyword(w http.ResponseWriter, r *http.Request) { + // not implemented + w.WriteHeader(http.StatusTeapot) +} + +// Create a resource +// (POST /resource/{argument}) +func (s *fakeServer) CreateResource(w http.ResponseWriter, r *http.Request, argument Argument) { + // not implemented + w.WriteHeader(http.StatusTeapot) +} + +// Create a resource with inline parameter +// (POST /resource2/{inline_argument}) +func (s *fakeServer) CreateResource2(w http.ResponseWriter, r *http.Request, inlineArgument int, params CreateResource2Params) { + assert.Equal(s.t, 99, *params.InlineQueryArgument) + assert.Equal(s.t, 1, inlineArgument) + s.called = true +} + +// Update a resource with inline body. The parameter name is a reserved +// keyword, so make sure that gets prefixed to avoid syntax errors +// (PUT /resource3/{fallthrough}) +func (s *fakeServer) UpdateResource3(w http.ResponseWriter, r *http.Request, pFallthrough int) { + // not implemented + w.WriteHeader(http.StatusTeapot) +} + +// get response with reference +// (GET /response-with-reference) +func (s *fakeServer) GetResponseWithReference(w http.ResponseWriter, r *http.Request) { + // not implemented + w.WriteHeader(http.StatusTeapot) +} + +func TestParameters(t *testing.T) { + m := fakeServer{ + t: t, } h := Handler(&m) @@ -24,11 +98,11 @@ func TestParameters(t *testing.T) { rr := httptest.NewRecorder() h.ServeHTTP(rr, req) - assert.Equal(t, 1, len(m.CreateResource2Calls())) + assert.True(t, m.called) } func TestErrorHandlerFunc(t *testing.T) { - m := ServerInterfaceMock{} + m := fakeServer{} h := HandlerWithOptions(&m, ChiServerOptions{ ErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { @@ -47,7 +121,7 @@ func TestErrorHandlerFunc(t *testing.T) { } func TestErrorHandlerFuncBackwardsCompatible(t *testing.T) { - m := ServerInterfaceMock{} + m := fakeServer{} h := HandlerWithOptions(&m, ChiServerOptions{}) diff --git a/internal/test/strict-server/chi/server.cfg.yaml b/internal/test/strict-server/chi/server.cfg.yaml index ca1c62c3c5..35425d6ec7 100644 --- a/internal/test/strict-server/chi/server.cfg.yaml +++ b/internal/test/strict-server/chi/server.cfg.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: api generate: chi-server: true diff --git a/internal/test/strict-server/chi/server.gen.go b/internal/test/strict-server/chi/server.gen.go index 124c057d2f..a7c569b2c5 100644 --- a/internal/test/strict-server/chi/server.gen.go +++ b/internal/test/strict-server/chi/server.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api import ( @@ -9,17 +9,20 @@ import ( "context" "encoding/base64" "encoding/json" + "errors" "fmt" "io" + "mime" "mime/multipart" "net/http" "net/url" "path" "strings" - "github.com/deepmap/oapi-codegen/pkg/runtime" "github.com/getkin/kin-openapi/openapi3" "github.com/go-chi/chi/v5" + "github.com/oapi-codegen/runtime" + strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp" ) // ServerInterface represents all server handlers. @@ -31,9 +34,21 @@ type ServerInterface interface { // (POST /multipart) MultipartExample(w http.ResponseWriter, r *http.Request) + // (POST /multipart-related) + MultipartRelatedExample(w http.ResponseWriter, r *http.Request) + // (POST /multiple) MultipleRequestAndResponseTypes(w http.ResponseWriter, r *http.Request) + // (POST /required-json-body) + RequiredJSONBody(w http.ResponseWriter, r *http.Request) + + // (POST /required-text-body) + RequiredTextBody(w http.ResponseWriter, r *http.Request) + + // (GET /reserved-go-keyword-parameters/{type}) + ReservedGoKeywordParameters(w http.ResponseWriter, r *http.Request, pType string) + // (POST /reusable-responses) ReusableResponses(w http.ResponseWriter, r *http.Request) @@ -51,6 +66,83 @@ type ServerInterface interface { // (POST /with-headers) HeadersExample(w http.ResponseWriter, r *http.Request, params HeadersExampleParams) + + // (POST /with-union) + UnionExample(w http.ResponseWriter, r *http.Request) +} + +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct{} + +// (POST /json) +func (_ Unimplemented) JSONExample(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// (POST /multipart) +func (_ Unimplemented) MultipartExample(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// (POST /multipart-related) +func (_ Unimplemented) MultipartRelatedExample(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// (POST /multiple) +func (_ Unimplemented) MultipleRequestAndResponseTypes(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// (POST /required-json-body) +func (_ Unimplemented) RequiredJSONBody(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// (POST /required-text-body) +func (_ Unimplemented) RequiredTextBody(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// (GET /reserved-go-keyword-parameters/{type}) +func (_ Unimplemented) ReservedGoKeywordParameters(w http.ResponseWriter, r *http.Request, pType string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// (POST /reusable-responses) +func (_ Unimplemented) ReusableResponses(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// (POST /text) +func (_ Unimplemented) TextExample(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// (POST /unknown) +func (_ Unimplemented) UnknownExample(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// (POST /unspecified-content-type) +func (_ Unimplemented) UnspecifiedContentType(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// (POST /urlencoded) +func (_ Unimplemented) URLEncodedExample(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// (POST /with-headers) +func (_ Unimplemented) HeadersExample(w http.ResponseWriter, r *http.Request, params HeadersExampleParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// (POST /with-union) +func (_ Unimplemented) UnionExample(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) } // ServerInterfaceWrapper converts contexts to parameters. @@ -64,127 +156,185 @@ type MiddlewareFunc func(http.Handler) http.Handler // JSONExample operation middleware func (siw *ServerInterfaceWrapper) JSONExample(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.JSONExample(w, r) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // MultipartExample operation middleware func (siw *ServerInterfaceWrapper) MultipartExample(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.MultipartExample(w, r) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) +} + +// MultipartRelatedExample operation middleware +func (siw *ServerInterfaceWrapper) MultipartRelatedExample(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.MultipartRelatedExample(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) } // MultipleRequestAndResponseTypes operation middleware func (siw *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.MultipleRequestAndResponseTypes(w, r) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) +} + +// RequiredJSONBody operation middleware +func (siw *ServerInterfaceWrapper) RequiredJSONBody(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.RequiredJSONBody(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// RequiredTextBody operation middleware +func (siw *ServerInterfaceWrapper) RequiredTextBody(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.RequiredTextBody(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ReservedGoKeywordParameters operation middleware +func (siw *ServerInterfaceWrapper) ReservedGoKeywordParameters(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "type" ------------- + var pType string + + err = runtime.BindStyledParameterWithOptions("simple", "type", chi.URLParam(r, "type"), &pType, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "type", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ReservedGoKeywordParameters(w, r, pType) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) } // ReusableResponses operation middleware func (siw *ServerInterfaceWrapper) ReusableResponses(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.ReusableResponses(w, r) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // TextExample operation middleware func (siw *ServerInterfaceWrapper) TextExample(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.TextExample(w, r) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // UnknownExample operation middleware func (siw *ServerInterfaceWrapper) UnknownExample(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.UnknownExample(w, r) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // UnspecifiedContentType operation middleware func (siw *ServerInterfaceWrapper) UnspecifiedContentType(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.UnspecifiedContentType(w, r) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // URLEncodedExample operation middleware func (siw *ServerInterfaceWrapper) URLEncodedExample(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.URLEncodedExample(w, r) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } // HeadersExample operation middleware func (siw *ServerInterfaceWrapper) HeadersExample(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() var err error @@ -202,7 +352,7 @@ func (siw *ServerInterfaceWrapper) HeadersExample(w http.ResponseWriter, r *http return } - err = runtime.BindStyledParameterWithLocation("simple", false, "header1", runtime.ParamLocationHeader, valueList[0], &Header1) + err = runtime.BindStyledParameterWithOptions("simple", "header1", valueList[0], &Header1, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: true, Type: "string", Format: ""}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "header1", Err: err}) return @@ -225,7 +375,7 @@ func (siw *ServerInterfaceWrapper) HeadersExample(w http.ResponseWriter, r *http return } - err = runtime.BindStyledParameterWithLocation("simple", false, "header2", runtime.ParamLocationHeader, valueList[0], &Header2) + err = runtime.BindStyledParameterWithOptions("simple", "header2", valueList[0], &Header2, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false, Type: "integer", Format: ""}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "header2", Err: err}) return @@ -235,15 +385,29 @@ func (siw *ServerInterfaceWrapper) HeadersExample(w http.ResponseWriter, r *http } - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.HeadersExample(w, r, params) - }) + })) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) +} + +// UnionExample operation middleware +func (siw *ServerInterfaceWrapper) UnionExample(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UnionExample(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) } type UnescapedCookieParamError struct { @@ -259,16 +423,16 @@ func (e *UnescapedCookieParamError) Unwrap() error { return e.Err } -type UnmarshallingParamError struct { +type UnmarshalingParamError struct { ParamName string Err error } -func (e *UnmarshallingParamError) Error() string { - return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) } -func (e *UnmarshallingParamError) Unwrap() error { +func (e *UnmarshalingParamError) Unwrap() error { return e.Err } @@ -365,9 +529,21 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/multipart", wrapper.MultipartExample) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/multipart-related", wrapper.MultipartRelatedExample) + }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/multiple", wrapper.MultipleRequestAndResponseTypes) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/required-json-body", wrapper.RequiredJSONBody) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/required-text-body", wrapper.RequiredTextBody) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/reserved-go-keyword-parameters/{type}", wrapper.ReservedGoKeywordParameters) + }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/reusable-responses", wrapper.ReusableResponses) }) @@ -386,6 +562,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/with-headers", wrapper.HeadersExample) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/with-union", wrapper.UnionExample) + }) return r } @@ -471,6 +650,41 @@ func (response MultipartExampledefaultResponse) VisitMultipartExampleResponse(w return nil } +type MultipartRelatedExampleRequestObject struct { + Body *multipart.Reader +} + +type MultipartRelatedExampleResponseObject interface { + VisitMultipartRelatedExampleResponse(w http.ResponseWriter) error +} + +type MultipartRelatedExample200MultipartResponse func(writer *multipart.Writer) error + +func (response MultipartRelatedExample200MultipartResponse) VisitMultipartRelatedExampleResponse(w http.ResponseWriter) error { + writer := multipart.NewWriter(w) + w.Header().Set("Content-Type", mime.FormatMediaType("multipart/related", map[string]string{"boundary": writer.Boundary()})) + w.WriteHeader(200) + + defer writer.Close() + return response(writer) +} + +type MultipartRelatedExample400Response = BadrequestResponse + +func (response MultipartRelatedExample400Response) VisitMultipartRelatedExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type MultipartRelatedExampledefaultResponse struct { + StatusCode int +} + +func (response MultipartRelatedExampledefaultResponse) VisitMultipartRelatedExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + type MultipleRequestAndResponseTypesRequestObject struct { JSONBody *MultipleRequestAndResponseTypesJSONRequestBody FormdataBody *MultipleRequestAndResponseTypesFormdataRequestBody @@ -553,6 +767,91 @@ func (response MultipleRequestAndResponseTypes400Response) VisitMultipleRequestA return nil } +type RequiredJSONBodyRequestObject struct { + Body *RequiredJSONBodyJSONRequestBody +} + +type RequiredJSONBodyResponseObject interface { + VisitRequiredJSONBodyResponse(w http.ResponseWriter) error +} + +type RequiredJSONBody200JSONResponse Example + +func (response RequiredJSONBody200JSONResponse) VisitRequiredJSONBodyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type RequiredJSONBody400Response = BadrequestResponse + +func (response RequiredJSONBody400Response) VisitRequiredJSONBodyResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type RequiredJSONBodydefaultResponse struct { + StatusCode int +} + +func (response RequiredJSONBodydefaultResponse) VisitRequiredJSONBodyResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type RequiredTextBodyRequestObject struct { + Body *RequiredTextBodyTextRequestBody +} + +type RequiredTextBodyResponseObject interface { + VisitRequiredTextBodyResponse(w http.ResponseWriter) error +} + +type RequiredTextBody200TextResponse string + +func (response RequiredTextBody200TextResponse) VisitRequiredTextBodyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(200) + + _, err := w.Write([]byte(response)) + return err +} + +type RequiredTextBody400Response = BadrequestResponse + +func (response RequiredTextBody400Response) VisitRequiredTextBodyResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type RequiredTextBodydefaultResponse struct { + StatusCode int +} + +func (response RequiredTextBodydefaultResponse) VisitRequiredTextBodyResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type ReservedGoKeywordParametersRequestObject struct { + Type string `json:"type"` +} + +type ReservedGoKeywordParametersResponseObject interface { + VisitReservedGoKeywordParametersResponse(w http.ResponseWriter) error +} + +type ReservedGoKeywordParameters200TextResponse string + +func (response ReservedGoKeywordParameters200TextResponse) VisitReservedGoKeywordParametersResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(200) + + _, err := w.Write([]byte(response)) + return err +} + type ReusableResponsesRequestObject struct { Body *ReusableResponsesJSONRequestBody } @@ -564,9 +863,9 @@ type ReusableResponsesResponseObject interface { type ReusableResponses200JSONResponse struct{ ReusableresponseJSONResponse } func (response ReusableResponses200JSONResponse) VisitReusableResponsesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) w.Header().Set("header2", fmt.Sprint(response.Headers.Header2)) - w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response.Body) @@ -784,9 +1083,9 @@ type HeadersExample200JSONResponse struct { } func (response HeadersExample200JSONResponse) VisitHeadersExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) w.Header().Set("header2", fmt.Sprint(response.Headers.Header2)) - w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response.Body) @@ -808,6 +1107,65 @@ func (response HeadersExampledefaultResponse) VisitHeadersExampleResponse(w http return nil } +type UnionExampleRequestObject struct { + Body *UnionExampleJSONRequestBody +} + +type UnionExampleResponseObject interface { + VisitUnionExampleResponse(w http.ResponseWriter) error +} + +type UnionExample200ResponseHeaders struct { + Header1 string + Header2 int +} + +type UnionExample200ApplicationAlternativePlusJSONResponse struct { + Body Example + Headers UnionExample200ResponseHeaders +} + +func (response UnionExample200ApplicationAlternativePlusJSONResponse) VisitUnionExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/alternative+json") + w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) + w.Header().Set("header2", fmt.Sprint(response.Headers.Header2)) + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response.Body) +} + +type UnionExample200JSONResponse struct { + Body struct { + union json.RawMessage + } + Headers UnionExample200ResponseHeaders +} + +func (response UnionExample200JSONResponse) VisitUnionExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) + w.Header().Set("header2", fmt.Sprint(response.Headers.Header2)) + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response.Body.union) +} + +type UnionExample400Response = BadrequestResponse + +func (response UnionExample400Response) VisitUnionExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type UnionExampledefaultResponse struct { + StatusCode int +} + +func (response UnionExampledefaultResponse) VisitUnionExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + // StrictServerInterface represents all server handlers. type StrictServerInterface interface { @@ -817,9 +1175,21 @@ type StrictServerInterface interface { // (POST /multipart) MultipartExample(ctx context.Context, request MultipartExampleRequestObject) (MultipartExampleResponseObject, error) + // (POST /multipart-related) + MultipartRelatedExample(ctx context.Context, request MultipartRelatedExampleRequestObject) (MultipartRelatedExampleResponseObject, error) + // (POST /multiple) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) + // (POST /required-json-body) + RequiredJSONBody(ctx context.Context, request RequiredJSONBodyRequestObject) (RequiredJSONBodyResponseObject, error) + + // (POST /required-text-body) + RequiredTextBody(ctx context.Context, request RequiredTextBodyRequestObject) (RequiredTextBodyResponseObject, error) + + // (GET /reserved-go-keyword-parameters/{type}) + ReservedGoKeywordParameters(ctx context.Context, request ReservedGoKeywordParametersRequestObject) (ReservedGoKeywordParametersResponseObject, error) + // (POST /reusable-responses) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) @@ -837,11 +1207,13 @@ type StrictServerInterface interface { // (POST /with-headers) HeadersExample(ctx context.Context, request HeadersExampleRequestObject) (HeadersExampleResponseObject, error) -} -type StrictHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, args interface{}) (interface{}, error) + // (POST /with-union) + UnionExample(ctx context.Context, request UnionExampleRequestObject) (UnionExampleResponseObject, error) +} -type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc +type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc +type StrictMiddlewareFunc = strictnethttp.StrictHTTPMiddlewareFunc type StrictHTTPServerOptions struct { RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) @@ -875,10 +1247,13 @@ func (sh *strictHandler) JSONExample(w http.ResponseWriter, r *http.Request) { var body JSONExampleJSONRequestBody if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) - return + if !errors.Is(err, io.EOF) { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + } else { + request.Body = &body } - request.Body = &body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { return sh.ssi.JSONExample(ctx, request.(JSONExampleRequestObject)) @@ -896,7 +1271,7 @@ func (sh *strictHandler) JSONExample(w http.ResponseWriter, r *http.Request) { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) } } @@ -927,7 +1302,41 @@ func (sh *strictHandler) MultipartExample(w http.ResponseWriter, r *http.Request sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// MultipartRelatedExample operation middleware +func (sh *strictHandler) MultipartRelatedExample(w http.ResponseWriter, r *http.Request) { + var request MultipartRelatedExampleRequestObject + + if _, params, err := mime.ParseMediaType(r.Header.Get("Content-Type")); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, err) + return + } else if boundary := params["boundary"]; boundary == "" { + sh.options.RequestErrorHandlerFunc(w, r, http.ErrMissingBoundary) + return + } else { + request.Body = multipart.NewReader(r.Body, boundary) + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.MultipartRelatedExample(ctx, request.(MultipartRelatedExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipartRelatedExample") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(MultipartRelatedExampleResponseObject); ok { + if err := validResponse.VisitMultipartRelatedExampleResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) } } @@ -936,12 +1345,16 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(w http.ResponseWriter, var request MultipleRequestAndResponseTypesRequestObject if strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") { + var body MultipleRequestAndResponseTypesJSONRequestBody if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) - return + if !errors.Is(err, io.EOF) { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + } else { + request.JSONBody = &body } - request.JSONBody = &body } if strings.HasPrefix(r.Header.Get("Content-Type"), "application/x-www-form-urlencoded") { if err := r.ParseForm(); err != nil { @@ -972,8 +1385,10 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(w http.ResponseWriter, sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't read body: %w", err)) return } - body := MultipleRequestAndResponseTypesTextRequestBody(data) - request.TextBody = &body + if len(data) > 0 { + body := MultipleRequestAndResponseTypesTextRequestBody(data) + request.TextBody = &body + } } handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { @@ -992,7 +1407,96 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(w http.ResponseWriter, sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// RequiredJSONBody operation middleware +func (sh *strictHandler) RequiredJSONBody(w http.ResponseWriter, r *http.Request) { + var request RequiredJSONBodyRequestObject + + var body RequiredJSONBodyJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.RequiredJSONBody(ctx, request.(RequiredJSONBodyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "RequiredJSONBody") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(RequiredJSONBodyResponseObject); ok { + if err := validResponse.VisitRequiredJSONBodyResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// RequiredTextBody operation middleware +func (sh *strictHandler) RequiredTextBody(w http.ResponseWriter, r *http.Request) { + var request RequiredTextBodyRequestObject + + data, err := io.ReadAll(r.Body) + if err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't read body: %w", err)) + return + } + body := RequiredTextBodyTextRequestBody(data) + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.RequiredTextBody(ctx, request.(RequiredTextBodyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "RequiredTextBody") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(RequiredTextBodyResponseObject); ok { + if err := validResponse.VisitRequiredTextBodyResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// ReservedGoKeywordParameters operation middleware +func (sh *strictHandler) ReservedGoKeywordParameters(w http.ResponseWriter, r *http.Request, pType string) { + var request ReservedGoKeywordParametersRequestObject + + request.Type = pType + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.ReservedGoKeywordParameters(ctx, request.(ReservedGoKeywordParametersRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReservedGoKeywordParameters") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(ReservedGoKeywordParametersResponseObject); ok { + if err := validResponse.VisitReservedGoKeywordParametersResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) } } @@ -1002,10 +1506,13 @@ func (sh *strictHandler) ReusableResponses(w http.ResponseWriter, r *http.Reques var body ReusableResponsesJSONRequestBody if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) - return + if !errors.Is(err, io.EOF) { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + } else { + request.Body = &body } - request.Body = &body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { return sh.ssi.ReusableResponses(ctx, request.(ReusableResponsesRequestObject)) @@ -1023,7 +1530,7 @@ func (sh *strictHandler) ReusableResponses(w http.ResponseWriter, r *http.Reques sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) } } @@ -1036,8 +1543,10 @@ func (sh *strictHandler) TextExample(w http.ResponseWriter, r *http.Request) { sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't read body: %w", err)) return } - body := TextExampleTextRequestBody(data) - request.Body = &body + if len(data) > 0 { + body := TextExampleTextRequestBody(data) + request.Body = &body + } handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { return sh.ssi.TextExample(ctx, request.(TextExampleRequestObject)) @@ -1055,7 +1564,7 @@ func (sh *strictHandler) TextExample(w http.ResponseWriter, r *http.Request) { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) } } @@ -1081,7 +1590,7 @@ func (sh *strictHandler) UnknownExample(w http.ResponseWriter, r *http.Request) sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) } } @@ -1109,7 +1618,7 @@ func (sh *strictHandler) UnspecifiedContentType(w http.ResponseWriter, r *http.R sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) } } @@ -1144,7 +1653,7 @@ func (sh *strictHandler) URLEncodedExample(w http.ResponseWriter, r *http.Reques sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) } } @@ -1156,10 +1665,13 @@ func (sh *strictHandler) HeadersExample(w http.ResponseWriter, r *http.Request, var body HeadersExampleJSONRequestBody if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) - return + if !errors.Is(err, io.EOF) { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + } else { + request.Body = &body } - request.Body = &body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { return sh.ssi.HeadersExample(ctx, request.(HeadersExampleRequestObject)) @@ -1177,28 +1689,66 @@ func (sh *strictHandler) HeadersExample(w http.ResponseWriter, r *http.Request, sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// UnionExample operation middleware +func (sh *strictHandler) UnionExample(w http.ResponseWriter, r *http.Request) { + var request UnionExampleRequestObject + + var body UnionExampleJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + if !errors.Is(err, io.EOF) { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + } else { + request.Body = &body + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.UnionExample(ctx, request.(UnionExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnionExample") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(UnionExampleResponseObject); ok { + if err := validResponse.VisitUnionExampleResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) } } // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xYS4/iOBD+K1btnkaB0D194rbTGmnfI9Ezp9UcirgAzya2166QRoj/vnJsaBjSCFo8", - "pNXeEqde/qq+KsdLKExljSbNHoZLcOSt0Z7alzFKR//U5Dm8SfKFU5aV0TCEDyhH6dsqA0e1x3FJa/Ug", - "XxjNpFtVtLZUBQbV/JsP+kvwxYwqDE8/OprAEH7IX0LJ41ef0zNWtiRYrVbZdxF8+g0ymBFKcm208fFu", - "1zYvLMEQPDulpxCMRLH7TjGlmabkgrcgmoIIAus4hkuwzlhyrCJGcyxr6vaUVsz4GxUcd6D0xOxj+Wg0", - "o9JeSDWZkCPNIoEngg0vfG2tcUxSjBcieChYeHJzcpABKw6BwdP2ukgBe8hgTs5HR3f9QX8Q8mUsabQK", - "hvC+XcrAIs/aDW0SZE1X3n99+vSnUF5gzaZCVgWW5UJU6PwMy5KkUJpNiLEu2PehdeXazP8ik/rHhGUo", - "m7aCPhi5uETFtIW5Vc/3g8GVCnOVwUN01mVjE1S+xbDWzATrsgP0L/pvbRotyDnj0s7yqi5ZWXS8naxd", - "tP9YixwD+cZePjGu6klkvBDq5/J0U+BTM+gkydPMNF7MTCPYCElYikbxTKwVv2O30gKFV3paklgHlXVm", - "sqTUc3/ScpT28jnYuDiXsh0rz72maXpt8mpXki6MJPk2s6rCKeVWT3fVg21kGMJ4waFs97vrmYooA6Zn", - "zm2JSh8eHVdqJ/8jfTZiR7quzya9neR1E3dNKi8K1GIc+DjxgcRdvvZIOkqeRlsStxlxhzHaO61do2uG", - "5L8+qT7T81FD6oxkvXY1ngpYHRdfxyxpHQPbG7l/BIpzJcnklX040fLNQPWWCjVRJHtpF70Y22st4dHo", - "whHvDu1wAtaGxcZYOJjzjEREIBPeiIZEVXsWFr0XitsuUqp4uJe01zy+vET2GD2FyX5EVt9dKKfvbpXR", - "h8Hd6SrvL1w3O8P3FT6Ofv8YZU79wznblD/xjHI+vzeiczhW97buALop/HMUeJnpBak5SYFaCkdcO01S", - "zBWuf1v3uJkMvKTVosOKuPX61xLCCEkXC5CBxoo273epCJQLyLKrKTt0PXHQ1j1kh+4svv6Hf6gvedNz", - "6TpdZRAvZWKx1K4MGWW2wzyPlzl93+B0Sq6vTI5Wwerr6t8AAAD//ygSomqZEwAA", + "H4sIAAAAAAAC/+xZTXPbNhP+Kzt431NKmrbjE2+NJ5O2aeuObJ86PkDESkJCAiiwFK3R6L93QEBfFq1K", + "qT4ynt5Ecr/wPLuLBTRlha6MVqjIsXzKLDqjlcP2oc+Fxb9qdOSfBLrCSkNSK5azD1z04rdZwizWjvdL", + "nKt7+UIrQtWqcmNKWXCvmn1xXn/KXDHCivtf/7c4YDn7X7YMJQtfXYbPvDIlstlslryI4O4zS9gIuUDb", + "Rht+Xq3bpolBljNHVqoh80aC2HWnmFSEQ7TemxeNQXiBeRz5lBmrDVqSAaMxL2vs9hTf6P4XLCisQKqB", + "3sTyViviUjkQcjBAi4ogggfehgNXG6MtoYD+BLyHgsChHaNlCSNJPjB2v/oeYsCOJWyM1gVHVxeXF5ee", + "L21QcSNZzt63rxJmOI3aBS0IMrqL91/u734H6YDXpCtOsuBlOYGKWzfiJQqQirQPsS7IXbDWk22J/1lE", + "7Y8RSp81bQJ90GJyjIRp83Ilna8vL0+Ul7OE3QRnXTYWQWUrBdaaGfC67MD8UX1VulGA1mobV5ZVdUnS", + "cEurXK2j/dtcZBfIF/aygbZVKjjxI6F+KE/nBj61WHJCsQMBvSC5Hw8r5o/Kwr/xc1YOYj/u7FP3I904", + "GOkGSINAXkIjaQRzxRcNVirg4KQalgjzoJJOMkuM296PSvTiWh68jaP3s2TNynPaNE3aFlBtS1SFFt9G", + "YcJkxYeYGTVcV/e2ObGc9SfkU3ZzgztQISeM8JkyU3Kptu/eJ2rp/yF9sMIO5eofpUWRekbSfqyP7sKN", + "5QVeyg8ac90EnIZKOl+l4aMb6boUwMuGT1zoD5sTRy+q+8mjLcyjjx3BgffJcrI1vu0xZEGtz6zzUPuA", + "z/SP1O6R+PvSd+qa2p+i9kwg0qFOv+Kk0VakhlteIaF12dTHOfO2hthh8o+FJBRcQR9B8QoF8AGhhU8a", + "oknXwU/w+0l/DiJLU+2BY/GQ/zllHrz2EMIS5h2wPOD3ko5kC8BPx6VqjmY46qZrrl5L+Cgyh87iwPmB", + "pIvjDvyCp96KxHmOTNtzc+Pwf4qk9ky+Pnj7lrDLsH3AweN77wJ1ePk6ZlFrF9i+cY7ZAcWxFKizytzs", + "aflsoDqDhRxIFGlcRRpie60l3GpVWKT1A4jfDJUmWBiD/gRohBAQaPfHBqGqHYHhzoGktouUMtwVCdxo", + "Ho/LyG6Dp4dlO93G6rsjcfruXIzeXF7tr/L+yHmzdpB4pR57v34MMvvemB3sxLLneetwfs9Uzo2kUbpy", + "pdxdwj8FgeWeXqAc+4lICbBItVUoYCz5/Bp0ozajgSWtXbNQCGM5Dc2vt/cZiJKttq5Zsu0K/OkNX9Ae", + "84+DU+VpreS2i/pH/xniDP1yb5BafafX8LwktIqTHOMPh7m/2bSiFd4N2kp7wXKyo4ent5dVs4SFf45C", + "C6pt6fsEkcmzLPzjdOEaPhyivZA640Z6FP4OAAD//0nTejA+HAAA", } // GetSwagger returns the content of the embedded swagger specification file @@ -1206,16 +1756,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -1233,7 +1783,7 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } @@ -1247,12 +1797,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/internal/test/strict-server/chi/server.go b/internal/test/strict-server/chi/server.go index 5b7026809e..914f967268 100644 --- a/internal/test/strict-server/chi/server.go +++ b/internal/test/strict-server/chi/server.go @@ -1,10 +1,11 @@ -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=server.cfg.yaml ../strict-schema.yaml -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=types.cfg.yaml ../strict-schema.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=server.cfg.yaml ../strict-schema.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=types.cfg.yaml ../strict-schema.yaml package api import ( "context" + "encoding/json" "io" "mime/multipart" ) @@ -40,6 +41,30 @@ func (s StrictServer) MultipartExample(ctx context.Context, request MultipartExa }), nil } +func (s StrictServer) MultipartRelatedExample(ctx context.Context, request MultipartRelatedExampleRequestObject) (MultipartRelatedExampleResponseObject, error) { + return MultipartRelatedExample200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.Body.NextPart() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + w, err := writer.CreatePart(part.Header) + if err != nil { + return err + } + _, err = io.Copy(w, part) + if err != nil { + return err + } + if err = part.Close(); err != nil { + return err + } + } + }), nil +} + func (s StrictServer) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) { switch { case request.Body != nil: @@ -100,3 +125,28 @@ func (s StrictServer) HeadersExample(ctx context.Context, request HeadersExample func (s StrictServer) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) { return ReusableResponses200JSONResponse{ReusableresponseJSONResponse: ReusableresponseJSONResponse{Body: *request.Body}}, nil } + +func (s StrictServer) RequiredJSONBody(ctx context.Context, request RequiredJSONBodyRequestObject) (RequiredJSONBodyResponseObject, error) { + return RequiredJSONBody200JSONResponse(*request.Body), nil +} + +func (s StrictServer) RequiredTextBody(ctx context.Context, request RequiredTextBodyRequestObject) (RequiredTextBodyResponseObject, error) { + return RequiredTextBody200TextResponse(*request.Body), nil +} + +func (s StrictServer) ReservedGoKeywordParameters(ctx context.Context, request ReservedGoKeywordParametersRequestObject) (ReservedGoKeywordParametersResponseObject, error) { + return ReservedGoKeywordParameters200TextResponse(""), nil +} + +func (s StrictServer) UnionExample(ctx context.Context, request UnionExampleRequestObject) (UnionExampleResponseObject, error) { + union, err := json.Marshal(*request.Body) + if err != nil { + return nil, err + } + + return UnionExample200JSONResponse{ + Body: struct{ union json.RawMessage }{ + union: union, + }, + }, nil +} diff --git a/internal/test/strict-server/chi/types.cfg.yaml b/internal/test/strict-server/chi/types.cfg.yaml index 4ea1d8aa5b..2ed9740ea7 100644 --- a/internal/test/strict-server/chi/types.cfg.yaml +++ b/internal/test/strict-server/chi/types.cfg.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: api generate: models: true diff --git a/internal/test/strict-server/chi/types.gen.go b/internal/test/strict-server/chi/types.gen.go index 33827cb7a4..fac014c4bb 100644 --- a/internal/test/strict-server/chi/types.gen.go +++ b/internal/test/strict-server/chi/types.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api // Example defines model for example. @@ -14,6 +14,9 @@ type Reusableresponse = Example // MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. type MultipleRequestAndResponseTypesTextBody = string +// RequiredTextBodyTextBody defines parameters for RequiredTextBody. +type RequiredTextBodyTextBody = string + // TextExampleTextBody defines parameters for TextExample. type TextExampleTextBody = string @@ -29,6 +32,9 @@ type JSONExampleJSONRequestBody = Example // MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. type MultipartExampleMultipartRequestBody = Example +// MultipartRelatedExampleMultipartRequestBody defines body for MultipartRelatedExample for multipart/related ContentType. +type MultipartRelatedExampleMultipartRequestBody = Example + // MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. type MultipleRequestAndResponseTypesJSONRequestBody = Example @@ -41,6 +47,12 @@ type MultipleRequestAndResponseTypesMultipartRequestBody = Example // MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. type MultipleRequestAndResponseTypesTextRequestBody = MultipleRequestAndResponseTypesTextBody +// RequiredJSONBodyJSONRequestBody defines body for RequiredJSONBody for application/json ContentType. +type RequiredJSONBodyJSONRequestBody = Example + +// RequiredTextBodyTextRequestBody defines body for RequiredTextBody for text/plain ContentType. +type RequiredTextBodyTextRequestBody = RequiredTextBodyTextBody + // ReusableResponsesJSONRequestBody defines body for ReusableResponses for application/json ContentType. type ReusableResponsesJSONRequestBody = Example @@ -52,3 +64,6 @@ type URLEncodedExampleFormdataRequestBody = Example // HeadersExampleJSONRequestBody defines body for HeadersExample for application/json ContentType. type HeadersExampleJSONRequestBody = Example + +// UnionExampleJSONRequestBody defines body for UnionExample for application/json ContentType. +type UnionExampleJSONRequestBody = Example diff --git a/internal/test/strict-server/client/client.cfg.yaml b/internal/test/strict-server/client/client.cfg.yaml index 637450eaa2..8437bde142 100644 --- a/internal/test/strict-server/client/client.cfg.yaml +++ b/internal/test/strict-server/client/client.cfg.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: api generate: models: true diff --git a/internal/test/strict-server/client/client.gen.go b/internal/test/strict-server/client/client.gen.go index 68105f2f24..b189ff5adf 100644 --- a/internal/test/strict-server/client/client.gen.go +++ b/internal/test/strict-server/client/client.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api import ( @@ -13,7 +13,7 @@ import ( "net/url" "strings" - "github.com/deepmap/oapi-codegen/pkg/runtime" + "github.com/oapi-codegen/runtime" ) // Example defines model for example. @@ -27,6 +27,9 @@ type Reusableresponse = Example // MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. type MultipleRequestAndResponseTypesTextBody = string +// RequiredTextBodyTextBody defines parameters for RequiredTextBody. +type RequiredTextBodyTextBody = string + // TextExampleTextBody defines parameters for TextExample. type TextExampleTextBody = string @@ -42,6 +45,9 @@ type JSONExampleJSONRequestBody = Example // MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. type MultipartExampleMultipartRequestBody = Example +// MultipartRelatedExampleMultipartRequestBody defines body for MultipartRelatedExample for multipart/related ContentType. +type MultipartRelatedExampleMultipartRequestBody = Example + // MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. type MultipleRequestAndResponseTypesJSONRequestBody = Example @@ -54,6 +60,12 @@ type MultipleRequestAndResponseTypesMultipartRequestBody = Example // MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. type MultipleRequestAndResponseTypesTextRequestBody = MultipleRequestAndResponseTypesTextBody +// RequiredJSONBodyJSONRequestBody defines body for RequiredJSONBody for application/json ContentType. +type RequiredJSONBodyJSONRequestBody = Example + +// RequiredTextBodyTextRequestBody defines body for RequiredTextBody for text/plain ContentType. +type RequiredTextBodyTextRequestBody = RequiredTextBodyTextBody + // ReusableResponsesJSONRequestBody defines body for ReusableResponses for application/json ContentType. type ReusableResponsesJSONRequestBody = Example @@ -66,6 +78,9 @@ type URLEncodedExampleFormdataRequestBody = Example // HeadersExampleJSONRequestBody defines body for HeadersExample for application/json ContentType. type HeadersExampleJSONRequestBody = Example +// UnionExampleJSONRequestBody defines body for UnionExample for application/json ContentType. +type UnionExampleJSONRequestBody = Example + // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -139,15 +154,18 @@ func WithRequestEditorFn(fn RequestEditorFn) ClientOption { // The interface specification for the client above. type ClientInterface interface { - // JSONExample request with any body + // JSONExampleWithBody request with any body JSONExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) JSONExample(ctx context.Context, body JSONExampleJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // MultipartExample request with any body + // MultipartExampleWithBody request with any body MultipartExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - // MultipleRequestAndResponseTypes request with any body + // MultipartRelatedExampleWithBody request with any body + MultipartRelatedExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + // MultipleRequestAndResponseTypesWithBody request with any body MultipleRequestAndResponseTypesWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) MultipleRequestAndResponseTypes(ctx context.Context, body MultipleRequestAndResponseTypesJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -156,31 +174,49 @@ type ClientInterface interface { MultipleRequestAndResponseTypesWithTextBody(ctx context.Context, body MultipleRequestAndResponseTypesTextRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // ReusableResponses request with any body + // RequiredJSONBodyWithBody request with any body + RequiredJSONBodyWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + RequiredJSONBody(ctx context.Context, body RequiredJSONBodyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // RequiredTextBodyWithBody request with any body + RequiredTextBodyWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + RequiredTextBodyWithTextBody(ctx context.Context, body RequiredTextBodyTextRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ReservedGoKeywordParameters request + ReservedGoKeywordParameters(ctx context.Context, pType string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ReusableResponsesWithBody request with any body ReusableResponsesWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) ReusableResponses(ctx context.Context, body ReusableResponsesJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // TextExample request with any body + // TextExampleWithBody request with any body TextExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) TextExampleWithTextBody(ctx context.Context, body TextExampleTextRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // UnknownExample request with any body + // UnknownExampleWithBody request with any body UnknownExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - // UnspecifiedContentType request with any body + // UnspecifiedContentTypeWithBody request with any body UnspecifiedContentTypeWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - // URLEncodedExample request with any body + // URLEncodedExampleWithBody request with any body URLEncodedExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) URLEncodedExampleWithFormdataBody(ctx context.Context, body URLEncodedExampleFormdataRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // HeadersExample request with any body + // HeadersExampleWithBody request with any body HeadersExampleWithBody(ctx context.Context, params *HeadersExampleParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) HeadersExample(ctx context.Context, params *HeadersExampleParams, body HeadersExampleJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UnionExampleWithBody request with any body + UnionExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + UnionExample(ctx context.Context, body UnionExampleJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) } func (c *Client) JSONExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -219,6 +255,18 @@ func (c *Client) MultipartExampleWithBody(ctx context.Context, contentType strin return c.Client.Do(req) } +func (c *Client) MultipartRelatedExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewMultipartRelatedExampleRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) MultipleRequestAndResponseTypesWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewMultipleRequestAndResponseTypesRequestWithBody(c.Server, contentType, body) if err != nil { @@ -267,6 +315,66 @@ func (c *Client) MultipleRequestAndResponseTypesWithTextBody(ctx context.Context return c.Client.Do(req) } +func (c *Client) RequiredJSONBodyWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRequiredJSONBodyRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) RequiredJSONBody(ctx context.Context, body RequiredJSONBodyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRequiredJSONBodyRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) RequiredTextBodyWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRequiredTextBodyRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) RequiredTextBodyWithTextBody(ctx context.Context, body RequiredTextBodyTextRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRequiredTextBodyRequestWithTextBody(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ReservedGoKeywordParameters(ctx context.Context, pType string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewReservedGoKeywordParametersRequest(c.Server, pType) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) ReusableResponsesWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewReusableResponsesRequestWithBody(c.Server, contentType, body) if err != nil { @@ -387,6 +495,30 @@ func (c *Client) HeadersExample(ctx context.Context, params *HeadersExampleParam return c.Client.Do(req) } +func (c *Client) UnionExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUnionExampleRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UnionExample(ctx context.Context, body UnionExampleJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUnionExampleRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + // NewJSONExampleRequest calls the generic JSONExample builder with application/json body func NewJSONExampleRequest(server string, body JSONExampleJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -456,6 +588,35 @@ func NewMultipartExampleRequestWithBody(server string, contentType string, body return req, nil } +// NewMultipartRelatedExampleRequestWithBody generates requests for MultipartRelatedExample with any type of body +func NewMultipartRelatedExampleRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/multipart-related") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewMultipleRequestAndResponseTypesRequest calls the generic MultipleRequestAndResponseTypes builder with application/json body func NewMultipleRequestAndResponseTypesRequest(server string, body MultipleRequestAndResponseTypesJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -514,6 +675,116 @@ func NewMultipleRequestAndResponseTypesRequestWithBody(server string, contentTyp return req, nil } +// NewRequiredJSONBodyRequest calls the generic RequiredJSONBody builder with application/json body +func NewRequiredJSONBodyRequest(server string, body RequiredJSONBodyJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewRequiredJSONBodyRequestWithBody(server, "application/json", bodyReader) +} + +// NewRequiredJSONBodyRequestWithBody generates requests for RequiredJSONBody with any type of body +func NewRequiredJSONBodyRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/required-json-body") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewRequiredTextBodyRequestWithTextBody calls the generic RequiredTextBody builder with text/plain body +func NewRequiredTextBodyRequestWithTextBody(server string, body RequiredTextBodyTextRequestBody) (*http.Request, error) { + var bodyReader io.Reader + bodyReader = strings.NewReader(string(body)) + return NewRequiredTextBodyRequestWithBody(server, "text/plain", bodyReader) +} + +// NewRequiredTextBodyRequestWithBody generates requests for RequiredTextBody with any type of body +func NewRequiredTextBodyRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/required-text-body") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewReservedGoKeywordParametersRequest generates requests for ReservedGoKeywordParameters +func NewReservedGoKeywordParametersRequest(server string, pType string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "type", pType, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/reserved-go-keyword-parameters/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewReusableResponsesRequest calls the generic ReusableResponses builder with application/json body func NewReusableResponsesRequest(server string, body ReusableResponsesJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -725,26 +996,70 @@ func NewHeadersExampleRequestWithBody(server string, params *HeadersExampleParam req.Header.Add("Content-Type", contentType) - var headerParam0 string + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "header1", params.Header1, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("header1", headerParam0) + + if params.Header2 != nil { + var headerParam1 string + + headerParam1, err = runtime.StyleParamWithOptions("simple", false, "header2", *params.Header2, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "integer", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("header2", headerParam1) + } + + } + + return req, nil +} - headerParam0, err = runtime.StyleParamWithLocation("simple", false, "header1", runtime.ParamLocationHeader, params.Header1) +// NewUnionExampleRequest calls the generic UnionExample builder with application/json body +func NewUnionExampleRequest(server string, body UnionExampleJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) if err != nil { return nil, err } + bodyReader = bytes.NewReader(buf) + return NewUnionExampleRequestWithBody(server, "application/json", bodyReader) +} - req.Header.Set("header1", headerParam0) +// NewUnionExampleRequestWithBody generates requests for UnionExample with any type of body +func NewUnionExampleRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error - if params.Header2 != nil { - var headerParam1 string + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } - headerParam1, err = runtime.StyleParamWithLocation("simple", false, "header2", runtime.ParamLocationHeader, *params.Header2) - if err != nil { - return nil, err - } + operationPath := fmt.Sprintf("/with-union") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } - req.Header.Set("header2", headerParam1) + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err } + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + return req, nil } @@ -791,15 +1106,18 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { - // JSONExample request with any body + // JSONExampleWithBodyWithResponse request with any body JSONExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*JSONExampleResponse, error) JSONExampleWithResponse(ctx context.Context, body JSONExampleJSONRequestBody, reqEditors ...RequestEditorFn) (*JSONExampleResponse, error) - // MultipartExample request with any body + // MultipartExampleWithBodyWithResponse request with any body MultipartExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MultipartExampleResponse, error) - // MultipleRequestAndResponseTypes request with any body + // MultipartRelatedExampleWithBodyWithResponse request with any body + MultipartRelatedExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MultipartRelatedExampleResponse, error) + + // MultipleRequestAndResponseTypesWithBodyWithResponse request with any body MultipleRequestAndResponseTypesWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MultipleRequestAndResponseTypesResponse, error) MultipleRequestAndResponseTypesWithResponse(ctx context.Context, body MultipleRequestAndResponseTypesJSONRequestBody, reqEditors ...RequestEditorFn) (*MultipleRequestAndResponseTypesResponse, error) @@ -808,31 +1126,49 @@ type ClientWithResponsesInterface interface { MultipleRequestAndResponseTypesWithTextBodyWithResponse(ctx context.Context, body MultipleRequestAndResponseTypesTextRequestBody, reqEditors ...RequestEditorFn) (*MultipleRequestAndResponseTypesResponse, error) - // ReusableResponses request with any body + // RequiredJSONBodyWithBodyWithResponse request with any body + RequiredJSONBodyWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RequiredJSONBodyResponse, error) + + RequiredJSONBodyWithResponse(ctx context.Context, body RequiredJSONBodyJSONRequestBody, reqEditors ...RequestEditorFn) (*RequiredJSONBodyResponse, error) + + // RequiredTextBodyWithBodyWithResponse request with any body + RequiredTextBodyWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RequiredTextBodyResponse, error) + + RequiredTextBodyWithTextBodyWithResponse(ctx context.Context, body RequiredTextBodyTextRequestBody, reqEditors ...RequestEditorFn) (*RequiredTextBodyResponse, error) + + // ReservedGoKeywordParametersWithResponse request + ReservedGoKeywordParametersWithResponse(ctx context.Context, pType string, reqEditors ...RequestEditorFn) (*ReservedGoKeywordParametersResponse, error) + + // ReusableResponsesWithBodyWithResponse request with any body ReusableResponsesWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ReusableResponsesResponse, error) ReusableResponsesWithResponse(ctx context.Context, body ReusableResponsesJSONRequestBody, reqEditors ...RequestEditorFn) (*ReusableResponsesResponse, error) - // TextExample request with any body + // TextExampleWithBodyWithResponse request with any body TextExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TextExampleResponse, error) TextExampleWithTextBodyWithResponse(ctx context.Context, body TextExampleTextRequestBody, reqEditors ...RequestEditorFn) (*TextExampleResponse, error) - // UnknownExample request with any body + // UnknownExampleWithBodyWithResponse request with any body UnknownExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UnknownExampleResponse, error) - // UnspecifiedContentType request with any body + // UnspecifiedContentTypeWithBodyWithResponse request with any body UnspecifiedContentTypeWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UnspecifiedContentTypeResponse, error) - // URLEncodedExample request with any body + // URLEncodedExampleWithBodyWithResponse request with any body URLEncodedExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*URLEncodedExampleResponse, error) URLEncodedExampleWithFormdataBodyWithResponse(ctx context.Context, body URLEncodedExampleFormdataRequestBody, reqEditors ...RequestEditorFn) (*URLEncodedExampleResponse, error) - // HeadersExample request with any body + // HeadersExampleWithBodyWithResponse request with any body HeadersExampleWithBodyWithResponse(ctx context.Context, params *HeadersExampleParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*HeadersExampleResponse, error) HeadersExampleWithResponse(ctx context.Context, params *HeadersExampleParams, body HeadersExampleJSONRequestBody, reqEditors ...RequestEditorFn) (*HeadersExampleResponse, error) + + // UnionExampleWithBodyWithResponse request with any body + UnionExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UnionExampleResponse, error) + + UnionExampleWithResponse(ctx context.Context, body UnionExampleJSONRequestBody, reqEditors ...RequestEditorFn) (*UnionExampleResponse, error) } type JSONExampleResponse struct { @@ -878,6 +1214,27 @@ func (r MultipartExampleResponse) StatusCode() int { return 0 } +type MultipartRelatedExampleResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r MultipartRelatedExampleResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r MultipartRelatedExampleResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type MultipleRequestAndResponseTypesResponse struct { Body []byte HTTPResponse *http.Response @@ -900,12 +1257,76 @@ func (r MultipleRequestAndResponseTypesResponse) StatusCode() int { return 0 } -type ReusableResponsesResponse struct { +type RequiredJSONBodyResponse struct { Body []byte HTTPResponse *http.Response JSON200 *Example } +// Status returns HTTPResponse.Status +func (r RequiredJSONBodyResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r RequiredJSONBodyResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type RequiredTextBodyResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r RequiredTextBodyResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r RequiredTextBodyResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type ReservedGoKeywordParametersResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r ReservedGoKeywordParametersResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ReservedGoKeywordParametersResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type ReusableResponsesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Reusableresponse +} + // Status returns HTTPResponse.Status func (r ReusableResponsesResponse) Status() string { if r.HTTPResponse != nil { @@ -1028,6 +1449,32 @@ func (r HeadersExampleResponse) StatusCode() int { return 0 } +type UnionExampleResponse struct { + Body []byte + HTTPResponse *http.Response + ApplicationalternativeJSON200 *Example + JSON200 *struct { + union json.RawMessage + } +} +type UnionExample2000 = string + +// Status returns HTTPResponse.Status +func (r UnionExampleResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UnionExampleResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + // JSONExampleWithBodyWithResponse request with arbitrary body returning *JSONExampleResponse func (c *ClientWithResponses) JSONExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*JSONExampleResponse, error) { rsp, err := c.JSONExampleWithBody(ctx, contentType, body, reqEditors...) @@ -1054,6 +1501,15 @@ func (c *ClientWithResponses) MultipartExampleWithBodyWithResponse(ctx context.C return ParseMultipartExampleResponse(rsp) } +// MultipartRelatedExampleWithBodyWithResponse request with arbitrary body returning *MultipartRelatedExampleResponse +func (c *ClientWithResponses) MultipartRelatedExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MultipartRelatedExampleResponse, error) { + rsp, err := c.MultipartRelatedExampleWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseMultipartRelatedExampleResponse(rsp) +} + // MultipleRequestAndResponseTypesWithBodyWithResponse request with arbitrary body returning *MultipleRequestAndResponseTypesResponse func (c *ClientWithResponses) MultipleRequestAndResponseTypesWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MultipleRequestAndResponseTypesResponse, error) { rsp, err := c.MultipleRequestAndResponseTypesWithBody(ctx, contentType, body, reqEditors...) @@ -1087,6 +1543,49 @@ func (c *ClientWithResponses) MultipleRequestAndResponseTypesWithTextBodyWithRes return ParseMultipleRequestAndResponseTypesResponse(rsp) } +// RequiredJSONBodyWithBodyWithResponse request with arbitrary body returning *RequiredJSONBodyResponse +func (c *ClientWithResponses) RequiredJSONBodyWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RequiredJSONBodyResponse, error) { + rsp, err := c.RequiredJSONBodyWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseRequiredJSONBodyResponse(rsp) +} + +func (c *ClientWithResponses) RequiredJSONBodyWithResponse(ctx context.Context, body RequiredJSONBodyJSONRequestBody, reqEditors ...RequestEditorFn) (*RequiredJSONBodyResponse, error) { + rsp, err := c.RequiredJSONBody(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseRequiredJSONBodyResponse(rsp) +} + +// RequiredTextBodyWithBodyWithResponse request with arbitrary body returning *RequiredTextBodyResponse +func (c *ClientWithResponses) RequiredTextBodyWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RequiredTextBodyResponse, error) { + rsp, err := c.RequiredTextBodyWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseRequiredTextBodyResponse(rsp) +} + +func (c *ClientWithResponses) RequiredTextBodyWithTextBodyWithResponse(ctx context.Context, body RequiredTextBodyTextRequestBody, reqEditors ...RequestEditorFn) (*RequiredTextBodyResponse, error) { + rsp, err := c.RequiredTextBodyWithTextBody(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseRequiredTextBodyResponse(rsp) +} + +// ReservedGoKeywordParametersWithResponse request returning *ReservedGoKeywordParametersResponse +func (c *ClientWithResponses) ReservedGoKeywordParametersWithResponse(ctx context.Context, pType string, reqEditors ...RequestEditorFn) (*ReservedGoKeywordParametersResponse, error) { + rsp, err := c.ReservedGoKeywordParameters(ctx, pType, reqEditors...) + if err != nil { + return nil, err + } + return ParseReservedGoKeywordParametersResponse(rsp) +} + // ReusableResponsesWithBodyWithResponse request with arbitrary body returning *ReusableResponsesResponse func (c *ClientWithResponses) ReusableResponsesWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ReusableResponsesResponse, error) { rsp, err := c.ReusableResponsesWithBody(ctx, contentType, body, reqEditors...) @@ -1173,6 +1672,23 @@ func (c *ClientWithResponses) HeadersExampleWithResponse(ctx context.Context, pa return ParseHeadersExampleResponse(rsp) } +// UnionExampleWithBodyWithResponse request with arbitrary body returning *UnionExampleResponse +func (c *ClientWithResponses) UnionExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UnionExampleResponse, error) { + rsp, err := c.UnionExampleWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUnionExampleResponse(rsp) +} + +func (c *ClientWithResponses) UnionExampleWithResponse(ctx context.Context, body UnionExampleJSONRequestBody, reqEditors ...RequestEditorFn) (*UnionExampleResponse, error) { + rsp, err := c.UnionExample(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUnionExampleResponse(rsp) +} + // ParseJSONExampleResponse parses an HTTP response from a JSONExampleWithResponse call func ParseJSONExampleResponse(rsp *http.Response) (*JSONExampleResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -1215,6 +1731,22 @@ func ParseMultipartExampleResponse(rsp *http.Response) (*MultipartExampleRespons return response, nil } +// ParseMultipartRelatedExampleResponse parses an HTTP response from a MultipartRelatedExampleWithResponse call +func ParseMultipartRelatedExampleResponse(rsp *http.Response) (*MultipartRelatedExampleResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &MultipartRelatedExampleResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + // ParseMultipleRequestAndResponseTypesResponse parses an HTTP response from a MultipleRequestAndResponseTypesWithResponse call func ParseMultipleRequestAndResponseTypesResponse(rsp *http.Response) (*MultipleRequestAndResponseTypesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -1244,6 +1776,64 @@ func ParseMultipleRequestAndResponseTypesResponse(rsp *http.Response) (*Multiple return response, nil } +// ParseRequiredJSONBodyResponse parses an HTTP response from a RequiredJSONBodyWithResponse call +func ParseRequiredJSONBodyResponse(rsp *http.Response) (*RequiredJSONBodyResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &RequiredJSONBodyResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Example + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseRequiredTextBodyResponse parses an HTTP response from a RequiredTextBodyWithResponse call +func ParseRequiredTextBodyResponse(rsp *http.Response) (*RequiredTextBodyResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &RequiredTextBodyResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseReservedGoKeywordParametersResponse parses an HTTP response from a ReservedGoKeywordParametersWithResponse call +func ParseReservedGoKeywordParametersResponse(rsp *http.Response) (*ReservedGoKeywordParametersResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ReservedGoKeywordParametersResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + // ParseReusableResponsesResponse parses an HTTP response from a ReusableResponsesWithResponse call func ParseReusableResponsesResponse(rsp *http.Response) (*ReusableResponsesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -1259,7 +1849,7 @@ func ParseReusableResponsesResponse(rsp *http.Response) (*ReusableResponsesRespo switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Example + var dest Reusableresponse if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -1359,3 +1949,38 @@ func ParseHeadersExampleResponse(rsp *http.Response) (*HeadersExampleResponse, e return response, nil } + +// ParseUnionExampleResponse parses an HTTP response from a UnionExampleWithResponse call +func ParseUnionExampleResponse(rsp *http.Response) (*UnionExampleResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UnionExampleResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case rsp.Header.Get("Content-Type") == "application/alternative+json" && rsp.StatusCode == 200: + var dest Example + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.ApplicationalternativeJSON200 = &dest + + case rsp.Header.Get("Content-Type") == "application/json" && rsp.StatusCode == 200: + var dest struct { + union json.RawMessage + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} diff --git a/internal/test/strict-server/client/client.go b/internal/test/strict-server/client/client.go index 3b37a171f0..5b350a4884 100644 --- a/internal/test/strict-server/client/client.go +++ b/internal/test/strict-server/client/client.go @@ -1,3 +1,3 @@ -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=client.cfg.yaml ../strict-schema.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=client.cfg.yaml ../strict-schema.yaml package api diff --git a/internal/test/strict-server/echo/server.cfg.yaml b/internal/test/strict-server/echo/server.cfg.yaml index 3c3c5c9c5d..143f57f078 100644 --- a/internal/test/strict-server/echo/server.cfg.yaml +++ b/internal/test/strict-server/echo/server.cfg.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: api generate: echo-server: true diff --git a/internal/test/strict-server/echo/server.gen.go b/internal/test/strict-server/echo/server.gen.go index 46531bd8a4..e8e335c74a 100644 --- a/internal/test/strict-server/echo/server.gen.go +++ b/internal/test/strict-server/echo/server.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api import ( @@ -9,17 +9,20 @@ import ( "context" "encoding/base64" "encoding/json" + "errors" "fmt" "io" + "mime" "mime/multipart" "net/http" "net/url" "path" "strings" - "github.com/deepmap/oapi-codegen/pkg/runtime" "github.com/getkin/kin-openapi/openapi3" "github.com/labstack/echo/v4" + "github.com/oapi-codegen/runtime" + strictecho "github.com/oapi-codegen/runtime/strictmiddleware/echo" ) // ServerInterface represents all server handlers. @@ -31,9 +34,21 @@ type ServerInterface interface { // (POST /multipart) MultipartExample(ctx echo.Context) error + // (POST /multipart-related) + MultipartRelatedExample(ctx echo.Context) error + // (POST /multiple) MultipleRequestAndResponseTypes(ctx echo.Context) error + // (POST /required-json-body) + RequiredJSONBody(ctx echo.Context) error + + // (POST /required-text-body) + RequiredTextBody(ctx echo.Context) error + + // (GET /reserved-go-keyword-parameters/{type}) + ReservedGoKeywordParameters(ctx echo.Context, pType string) error + // (POST /reusable-responses) ReusableResponses(ctx echo.Context) error @@ -51,6 +66,9 @@ type ServerInterface interface { // (POST /with-headers) HeadersExample(ctx echo.Context, params HeadersExampleParams) error + + // (POST /with-union) + UnionExample(ctx echo.Context) error } // ServerInterfaceWrapper converts echo contexts to parameters. @@ -62,7 +80,7 @@ type ServerInterfaceWrapper struct { func (w *ServerInterfaceWrapper) JSONExample(ctx echo.Context) error { var err error - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.JSONExample(ctx) return err } @@ -71,25 +89,68 @@ func (w *ServerInterfaceWrapper) JSONExample(ctx echo.Context) error { func (w *ServerInterfaceWrapper) MultipartExample(ctx echo.Context) error { var err error - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.MultipartExample(ctx) return err } +// MultipartRelatedExample converts echo context to params. +func (w *ServerInterfaceWrapper) MultipartRelatedExample(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.MultipartRelatedExample(ctx) + return err +} + // MultipleRequestAndResponseTypes converts echo context to params. func (w *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(ctx echo.Context) error { var err error - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.MultipleRequestAndResponseTypes(ctx) return err } +// RequiredJSONBody converts echo context to params. +func (w *ServerInterfaceWrapper) RequiredJSONBody(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.RequiredJSONBody(ctx) + return err +} + +// RequiredTextBody converts echo context to params. +func (w *ServerInterfaceWrapper) RequiredTextBody(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.RequiredTextBody(ctx) + return err +} + +// ReservedGoKeywordParameters converts echo context to params. +func (w *ServerInterfaceWrapper) ReservedGoKeywordParameters(ctx echo.Context) error { + var err error + // ------------- Path parameter "type" ------------- + var pType string + + err = runtime.BindStyledParameterWithOptions("simple", "type", ctx.Param("type"), &pType, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter type: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.ReservedGoKeywordParameters(ctx, pType) + return err +} + // ReusableResponses converts echo context to params. func (w *ServerInterfaceWrapper) ReusableResponses(ctx echo.Context) error { var err error - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.ReusableResponses(ctx) return err } @@ -98,7 +159,7 @@ func (w *ServerInterfaceWrapper) ReusableResponses(ctx echo.Context) error { func (w *ServerInterfaceWrapper) TextExample(ctx echo.Context) error { var err error - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.TextExample(ctx) return err } @@ -107,7 +168,7 @@ func (w *ServerInterfaceWrapper) TextExample(ctx echo.Context) error { func (w *ServerInterfaceWrapper) UnknownExample(ctx echo.Context) error { var err error - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.UnknownExample(ctx) return err } @@ -116,7 +177,7 @@ func (w *ServerInterfaceWrapper) UnknownExample(ctx echo.Context) error { func (w *ServerInterfaceWrapper) UnspecifiedContentType(ctx echo.Context) error { var err error - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.UnspecifiedContentType(ctx) return err } @@ -125,7 +186,7 @@ func (w *ServerInterfaceWrapper) UnspecifiedContentType(ctx echo.Context) error func (w *ServerInterfaceWrapper) URLEncodedExample(ctx echo.Context) error { var err error - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.URLEncodedExample(ctx) return err } @@ -146,7 +207,7 @@ func (w *ServerInterfaceWrapper) HeadersExample(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for header1, got %d", n)) } - err = runtime.BindStyledParameterWithLocation("simple", false, "header1", runtime.ParamLocationHeader, valueList[0], &Header1) + err = runtime.BindStyledParameterWithOptions("simple", "header1", valueList[0], &Header1, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: true, Type: "string", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter header1: %s", err)) } @@ -163,7 +224,7 @@ func (w *ServerInterfaceWrapper) HeadersExample(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for header2, got %d", n)) } - err = runtime.BindStyledParameterWithLocation("simple", false, "header2", runtime.ParamLocationHeader, valueList[0], &Header2) + err = runtime.BindStyledParameterWithOptions("simple", "header2", valueList[0], &Header2, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false, Type: "integer", Format: ""}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter header2: %s", err)) } @@ -171,11 +232,20 @@ func (w *ServerInterfaceWrapper) HeadersExample(ctx echo.Context) error { params.Header2 = &Header2 } - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.HeadersExample(ctx, params) return err } +// UnionExample converts echo context to params. +func (w *ServerInterfaceWrapper) UnionExample(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.UnionExample(ctx) + return err +} + // This is a simple interface which specifies echo.Route addition functions which // are present on both echo.Echo and echo.Group, since we want to allow using // either of them for path registration @@ -206,13 +276,18 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.POST(baseURL+"/json", wrapper.JSONExample) router.POST(baseURL+"/multipart", wrapper.MultipartExample) + router.POST(baseURL+"/multipart-related", wrapper.MultipartRelatedExample) router.POST(baseURL+"/multiple", wrapper.MultipleRequestAndResponseTypes) + router.POST(baseURL+"/required-json-body", wrapper.RequiredJSONBody) + router.POST(baseURL+"/required-text-body", wrapper.RequiredTextBody) + router.GET(baseURL+"/reserved-go-keyword-parameters/:type", wrapper.ReservedGoKeywordParameters) router.POST(baseURL+"/reusable-responses", wrapper.ReusableResponses) router.POST(baseURL+"/text", wrapper.TextExample) router.POST(baseURL+"/unknown", wrapper.UnknownExample) router.POST(baseURL+"/unspecified-content-type", wrapper.UnspecifiedContentType) router.POST(baseURL+"/urlencoded", wrapper.URLEncodedExample) router.POST(baseURL+"/with-headers", wrapper.HeadersExample) + router.POST(baseURL+"/with-union", wrapper.UnionExample) } @@ -297,6 +372,41 @@ func (response MultipartExampledefaultResponse) VisitMultipartExampleResponse(w return nil } +type MultipartRelatedExampleRequestObject struct { + Body *multipart.Reader +} + +type MultipartRelatedExampleResponseObject interface { + VisitMultipartRelatedExampleResponse(w http.ResponseWriter) error +} + +type MultipartRelatedExample200MultipartResponse func(writer *multipart.Writer) error + +func (response MultipartRelatedExample200MultipartResponse) VisitMultipartRelatedExampleResponse(w http.ResponseWriter) error { + writer := multipart.NewWriter(w) + w.Header().Set("Content-Type", mime.FormatMediaType("multipart/related", map[string]string{"boundary": writer.Boundary()})) + w.WriteHeader(200) + + defer writer.Close() + return response(writer) +} + +type MultipartRelatedExample400Response = BadrequestResponse + +func (response MultipartRelatedExample400Response) VisitMultipartRelatedExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type MultipartRelatedExampledefaultResponse struct { + StatusCode int +} + +func (response MultipartRelatedExampledefaultResponse) VisitMultipartRelatedExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + type MultipleRequestAndResponseTypesRequestObject struct { JSONBody *MultipleRequestAndResponseTypesJSONRequestBody FormdataBody *MultipleRequestAndResponseTypesFormdataRequestBody @@ -379,6 +489,91 @@ func (response MultipleRequestAndResponseTypes400Response) VisitMultipleRequestA return nil } +type RequiredJSONBodyRequestObject struct { + Body *RequiredJSONBodyJSONRequestBody +} + +type RequiredJSONBodyResponseObject interface { + VisitRequiredJSONBodyResponse(w http.ResponseWriter) error +} + +type RequiredJSONBody200JSONResponse Example + +func (response RequiredJSONBody200JSONResponse) VisitRequiredJSONBodyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type RequiredJSONBody400Response = BadrequestResponse + +func (response RequiredJSONBody400Response) VisitRequiredJSONBodyResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type RequiredJSONBodydefaultResponse struct { + StatusCode int +} + +func (response RequiredJSONBodydefaultResponse) VisitRequiredJSONBodyResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type RequiredTextBodyRequestObject struct { + Body *RequiredTextBodyTextRequestBody +} + +type RequiredTextBodyResponseObject interface { + VisitRequiredTextBodyResponse(w http.ResponseWriter) error +} + +type RequiredTextBody200TextResponse string + +func (response RequiredTextBody200TextResponse) VisitRequiredTextBodyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(200) + + _, err := w.Write([]byte(response)) + return err +} + +type RequiredTextBody400Response = BadrequestResponse + +func (response RequiredTextBody400Response) VisitRequiredTextBodyResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type RequiredTextBodydefaultResponse struct { + StatusCode int +} + +func (response RequiredTextBodydefaultResponse) VisitRequiredTextBodyResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type ReservedGoKeywordParametersRequestObject struct { + Type string `json:"type"` +} + +type ReservedGoKeywordParametersResponseObject interface { + VisitReservedGoKeywordParametersResponse(w http.ResponseWriter) error +} + +type ReservedGoKeywordParameters200TextResponse string + +func (response ReservedGoKeywordParameters200TextResponse) VisitReservedGoKeywordParametersResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(200) + + _, err := w.Write([]byte(response)) + return err +} + type ReusableResponsesRequestObject struct { Body *ReusableResponsesJSONRequestBody } @@ -390,9 +585,9 @@ type ReusableResponsesResponseObject interface { type ReusableResponses200JSONResponse struct{ ReusableresponseJSONResponse } func (response ReusableResponses200JSONResponse) VisitReusableResponsesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) w.Header().Set("header2", fmt.Sprint(response.Headers.Header2)) - w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response.Body) @@ -610,9 +805,9 @@ type HeadersExample200JSONResponse struct { } func (response HeadersExample200JSONResponse) VisitHeadersExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) w.Header().Set("header2", fmt.Sprint(response.Headers.Header2)) - w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response.Body) @@ -634,6 +829,65 @@ func (response HeadersExampledefaultResponse) VisitHeadersExampleResponse(w http return nil } +type UnionExampleRequestObject struct { + Body *UnionExampleJSONRequestBody +} + +type UnionExampleResponseObject interface { + VisitUnionExampleResponse(w http.ResponseWriter) error +} + +type UnionExample200ResponseHeaders struct { + Header1 string + Header2 int +} + +type UnionExample200ApplicationAlternativePlusJSONResponse struct { + Body Example + Headers UnionExample200ResponseHeaders +} + +func (response UnionExample200ApplicationAlternativePlusJSONResponse) VisitUnionExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/alternative+json") + w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) + w.Header().Set("header2", fmt.Sprint(response.Headers.Header2)) + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response.Body) +} + +type UnionExample200JSONResponse struct { + Body struct { + union json.RawMessage + } + Headers UnionExample200ResponseHeaders +} + +func (response UnionExample200JSONResponse) VisitUnionExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) + w.Header().Set("header2", fmt.Sprint(response.Headers.Header2)) + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response.Body.union) +} + +type UnionExample400Response = BadrequestResponse + +func (response UnionExample400Response) VisitUnionExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type UnionExampledefaultResponse struct { + StatusCode int +} + +func (response UnionExampledefaultResponse) VisitUnionExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + // StrictServerInterface represents all server handlers. type StrictServerInterface interface { @@ -643,9 +897,21 @@ type StrictServerInterface interface { // (POST /multipart) MultipartExample(ctx context.Context, request MultipartExampleRequestObject) (MultipartExampleResponseObject, error) + // (POST /multipart-related) + MultipartRelatedExample(ctx context.Context, request MultipartRelatedExampleRequestObject) (MultipartRelatedExampleResponseObject, error) + // (POST /multiple) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) + // (POST /required-json-body) + RequiredJSONBody(ctx context.Context, request RequiredJSONBodyRequestObject) (RequiredJSONBodyResponseObject, error) + + // (POST /required-text-body) + RequiredTextBody(ctx context.Context, request RequiredTextBodyRequestObject) (RequiredTextBodyResponseObject, error) + + // (GET /reserved-go-keyword-parameters/{type}) + ReservedGoKeywordParameters(ctx context.Context, request ReservedGoKeywordParametersRequestObject) (ReservedGoKeywordParametersResponseObject, error) + // (POST /reusable-responses) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) @@ -663,11 +929,13 @@ type StrictServerInterface interface { // (POST /with-headers) HeadersExample(ctx context.Context, request HeadersExampleRequestObject) (HeadersExampleResponseObject, error) -} -type StrictHandlerFunc func(ctx echo.Context, args interface{}) (interface{}, error) + // (POST /with-union) + UnionExample(ctx context.Context, request UnionExampleRequestObject) (UnionExampleResponseObject, error) +} -type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc +type StrictHandlerFunc = strictecho.StrictEchoHandlerFunc +type StrictMiddlewareFunc = strictecho.StrictEchoMiddlewareFunc func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { return &strictHandler{ssi: ssi, middlewares: middlewares} @@ -684,9 +952,12 @@ func (sh *strictHandler) JSONExample(ctx echo.Context) error { var body JSONExampleJSONRequestBody if err := ctx.Bind(&body); err != nil { - return err + if !errors.Is(err, io.EOF) { + return err + } + } else { + request.Body = &body } - request.Body = &body handler := func(ctx echo.Context, request interface{}) (interface{}, error) { return sh.ssi.JSONExample(ctx.Request().Context(), request.(JSONExampleRequestObject)) @@ -702,7 +973,7 @@ func (sh *strictHandler) JSONExample(ctx echo.Context) error { } else if validResponse, ok := response.(JSONExampleResponseObject); ok { return validResponse.VisitJSONExampleResponse(ctx.Response()) } else if response != nil { - return fmt.Errorf("Unexpected response type: %T", response) + return fmt.Errorf("unexpected response type: %T", response) } return nil } @@ -731,7 +1002,38 @@ func (sh *strictHandler) MultipartExample(ctx echo.Context) error { } else if validResponse, ok := response.(MultipartExampleResponseObject); ok { return validResponse.VisitMultipartExampleResponse(ctx.Response()) } else if response != nil { - return fmt.Errorf("Unexpected response type: %T", response) + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// MultipartRelatedExample operation middleware +func (sh *strictHandler) MultipartRelatedExample(ctx echo.Context) error { + var request MultipartRelatedExampleRequestObject + + if _, params, err := mime.ParseMediaType(ctx.Request().Header.Get("Content-Type")); err != nil { + return err + } else if boundary := params["boundary"]; boundary == "" { + return http.ErrMissingBoundary + } else { + request.Body = multipart.NewReader(ctx.Request().Body, boundary) + } + + handler := func(ctx echo.Context, request interface{}) (interface{}, error) { + return sh.ssi.MultipartRelatedExample(ctx.Request().Context(), request.(MultipartRelatedExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipartRelatedExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(MultipartRelatedExampleResponseObject); ok { + return validResponse.VisitMultipartRelatedExampleResponse(ctx.Response()) + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) } return nil } @@ -743,9 +1045,12 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx echo.Context) error if strings.HasPrefix(ctx.Request().Header.Get("Content-Type"), "application/json") { var body MultipleRequestAndResponseTypesJSONRequestBody if err := ctx.Bind(&body); err != nil { - return err + if !errors.Is(err, io.EOF) { + return err + } + } else { + request.JSONBody = &body } - request.JSONBody = &body } if strings.HasPrefix(ctx.Request().Header.Get("Content-Type"), "application/x-www-form-urlencoded") { if form, err := ctx.FormParams(); err == nil { @@ -773,8 +1078,10 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx echo.Context) error if err != nil { return err } - body := MultipleRequestAndResponseTypesTextRequestBody(data) - request.TextBody = &body + if len(data) > 0 { + body := MultipleRequestAndResponseTypesTextRequestBody(data) + request.TextBody = &body + } } handler := func(ctx echo.Context, request interface{}) (interface{}, error) { @@ -791,7 +1098,91 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx echo.Context) error } else if validResponse, ok := response.(MultipleRequestAndResponseTypesResponseObject); ok { return validResponse.VisitMultipleRequestAndResponseTypesResponse(ctx.Response()) } else if response != nil { - return fmt.Errorf("Unexpected response type: %T", response) + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// RequiredJSONBody operation middleware +func (sh *strictHandler) RequiredJSONBody(ctx echo.Context) error { + var request RequiredJSONBodyRequestObject + + var body RequiredJSONBodyJSONRequestBody + if err := ctx.Bind(&body); err != nil { + return err + } + request.Body = &body + + handler := func(ctx echo.Context, request interface{}) (interface{}, error) { + return sh.ssi.RequiredJSONBody(ctx.Request().Context(), request.(RequiredJSONBodyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "RequiredJSONBody") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(RequiredJSONBodyResponseObject); ok { + return validResponse.VisitRequiredJSONBodyResponse(ctx.Response()) + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// RequiredTextBody operation middleware +func (sh *strictHandler) RequiredTextBody(ctx echo.Context) error { + var request RequiredTextBodyRequestObject + + data, err := io.ReadAll(ctx.Request().Body) + if err != nil { + return err + } + body := RequiredTextBodyTextRequestBody(data) + request.Body = &body + + handler := func(ctx echo.Context, request interface{}) (interface{}, error) { + return sh.ssi.RequiredTextBody(ctx.Request().Context(), request.(RequiredTextBodyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "RequiredTextBody") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(RequiredTextBodyResponseObject); ok { + return validResponse.VisitRequiredTextBodyResponse(ctx.Response()) + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// ReservedGoKeywordParameters operation middleware +func (sh *strictHandler) ReservedGoKeywordParameters(ctx echo.Context, pType string) error { + var request ReservedGoKeywordParametersRequestObject + + request.Type = pType + + handler := func(ctx echo.Context, request interface{}) (interface{}, error) { + return sh.ssi.ReservedGoKeywordParameters(ctx.Request().Context(), request.(ReservedGoKeywordParametersRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReservedGoKeywordParameters") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(ReservedGoKeywordParametersResponseObject); ok { + return validResponse.VisitReservedGoKeywordParametersResponse(ctx.Response()) + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) } return nil } @@ -802,9 +1193,12 @@ func (sh *strictHandler) ReusableResponses(ctx echo.Context) error { var body ReusableResponsesJSONRequestBody if err := ctx.Bind(&body); err != nil { - return err + if !errors.Is(err, io.EOF) { + return err + } + } else { + request.Body = &body } - request.Body = &body handler := func(ctx echo.Context, request interface{}) (interface{}, error) { return sh.ssi.ReusableResponses(ctx.Request().Context(), request.(ReusableResponsesRequestObject)) @@ -820,7 +1214,7 @@ func (sh *strictHandler) ReusableResponses(ctx echo.Context) error { } else if validResponse, ok := response.(ReusableResponsesResponseObject); ok { return validResponse.VisitReusableResponsesResponse(ctx.Response()) } else if response != nil { - return fmt.Errorf("Unexpected response type: %T", response) + return fmt.Errorf("unexpected response type: %T", response) } return nil } @@ -833,8 +1227,10 @@ func (sh *strictHandler) TextExample(ctx echo.Context) error { if err != nil { return err } - body := TextExampleTextRequestBody(data) - request.Body = &body + if len(data) > 0 { + body := TextExampleTextRequestBody(data) + request.Body = &body + } handler := func(ctx echo.Context, request interface{}) (interface{}, error) { return sh.ssi.TextExample(ctx.Request().Context(), request.(TextExampleRequestObject)) @@ -850,7 +1246,7 @@ func (sh *strictHandler) TextExample(ctx echo.Context) error { } else if validResponse, ok := response.(TextExampleResponseObject); ok { return validResponse.VisitTextExampleResponse(ctx.Response()) } else if response != nil { - return fmt.Errorf("Unexpected response type: %T", response) + return fmt.Errorf("unexpected response type: %T", response) } return nil } @@ -875,7 +1271,7 @@ func (sh *strictHandler) UnknownExample(ctx echo.Context) error { } else if validResponse, ok := response.(UnknownExampleResponseObject); ok { return validResponse.VisitUnknownExampleResponse(ctx.Response()) } else if response != nil { - return fmt.Errorf("Unexpected response type: %T", response) + return fmt.Errorf("unexpected response type: %T", response) } return nil } @@ -902,7 +1298,7 @@ func (sh *strictHandler) UnspecifiedContentType(ctx echo.Context) error { } else if validResponse, ok := response.(UnspecifiedContentTypeResponseObject); ok { return validResponse.VisitUnspecifiedContentTypeResponse(ctx.Response()) } else if response != nil { - return fmt.Errorf("Unexpected response type: %T", response) + return fmt.Errorf("unexpected response type: %T", response) } return nil } @@ -935,7 +1331,7 @@ func (sh *strictHandler) URLEncodedExample(ctx echo.Context) error { } else if validResponse, ok := response.(URLEncodedExampleResponseObject); ok { return validResponse.VisitURLEncodedExampleResponse(ctx.Response()) } else if response != nil { - return fmt.Errorf("Unexpected response type: %T", response) + return fmt.Errorf("unexpected response type: %T", response) } return nil } @@ -948,9 +1344,12 @@ func (sh *strictHandler) HeadersExample(ctx echo.Context, params HeadersExampleP var body HeadersExampleJSONRequestBody if err := ctx.Bind(&body); err != nil { - return err + if !errors.Is(err, io.EOF) { + return err + } + } else { + request.Body = &body } - request.Body = &body handler := func(ctx echo.Context, request interface{}) (interface{}, error) { return sh.ssi.HeadersExample(ctx.Request().Context(), request.(HeadersExampleRequestObject)) @@ -966,7 +1365,39 @@ func (sh *strictHandler) HeadersExample(ctx echo.Context, params HeadersExampleP } else if validResponse, ok := response.(HeadersExampleResponseObject); ok { return validResponse.VisitHeadersExampleResponse(ctx.Response()) } else if response != nil { - return fmt.Errorf("Unexpected response type: %T", response) + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// UnionExample operation middleware +func (sh *strictHandler) UnionExample(ctx echo.Context) error { + var request UnionExampleRequestObject + + var body UnionExampleJSONRequestBody + if err := ctx.Bind(&body); err != nil { + if !errors.Is(err, io.EOF) { + return err + } + } else { + request.Body = &body + } + + handler := func(ctx echo.Context, request interface{}) (interface{}, error) { + return sh.ssi.UnionExample(ctx.Request().Context(), request.(UnionExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnionExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(UnionExampleResponseObject); ok { + return validResponse.VisitUnionExampleResponse(ctx.Response()) + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) } return nil } @@ -974,21 +1405,25 @@ func (sh *strictHandler) HeadersExample(ctx echo.Context, params HeadersExampleP // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xYS4/iOBD+K1btnkaB0D194rbTGmnfI9Ezp9UcirgAzya2166QRoj/vnJsaBjSCFo8", - "pNXeEqde/qq+KsdLKExljSbNHoZLcOSt0Z7alzFKR//U5Dm8SfKFU5aV0TCEDyhH6dsqA0e1x3FJa/Ug", - "XxjNpFtVtLZUBQbV/JsP+kvwxYwqDE8/OprAEH7IX0LJ41ef0zNWtiRYrVbZdxF8+g0ymBFKcm208fFu", - "1zYvLMEQPDulpxCMRLH7TjGlmabkgrcgmoIIAus4hkuwzlhyrCJGcyxr6vaUVsz4GxUcd6D0xOxj+Wg0", - "o9JeSDWZkCPNIoEngg0vfG2tcUxSjBcieChYeHJzcpABKw6BwdP2ukgBe8hgTs5HR3f9QX8Q8mUsabQK", - "hvC+XcrAIs/aDW0SZE1X3n99+vSnUF5gzaZCVgWW5UJU6PwMy5KkUJpNiLEu2PehdeXazP8ik/rHhGUo", - "m7aCPhi5uETFtIW5Vc/3g8GVCnOVwUN01mVjE1S+xbDWzATrsgP0L/pvbRotyDnj0s7yqi5ZWXS8naxd", - "tP9YixwD+cZePjGu6klkvBDq5/J0U+BTM+gkydPMNF7MTCPYCElYikbxTKwVv2O30gKFV3paklgHlXVm", - "sqTUc3/ScpT28jnYuDiXsh0rz72maXpt8mpXki6MJPk2s6rCKeVWT3fVg21kGMJ4waFs97vrmYooA6Zn", - "zm2JSh8eHVdqJ/8jfTZiR7quzya9neR1E3dNKi8K1GIc+DjxgcRdvvZIOkqeRlsStxlxhzHaO61do2uG", - "5L8+qT7T81FD6oxkvXY1ngpYHRdfxyxpHQPbG7l/BIpzJcnklX040fLNQPWWCjVRJHtpF70Y22st4dHo", - "whHvDu1wAtaGxcZYOJjzjEREIBPeiIZEVXsWFr0XitsuUqp4uJe01zy+vET2GD2FyX5EVt9dKKfvbpXR", - "h8Hd6SrvL1w3O8P3FT6Ofv8YZU79wznblD/xjHI+vzeiczhW97buALop/HMUeJnpBak5SYFaCkdcO01S", - "zBWuf1v3uJkMvKTVosOKuPX61xLCCEkXC5CBxoo273epCJQLyLKrKTt0PXHQ1j1kh+4svv6Hf6gvedNz", - "6TpdZRAvZWKx1K4MGWW2wzyPlzl93+B0Sq6vTI5Wwerr6t8AAAD//ygSomqZEwAA", + "H4sIAAAAAAAC/+xZTXPbNhP+Kzt431NKmrbjE2+NJ5O2aeuObJ86PkDESkJCAiiwFK3R6L93QEBfFq1K", + "qT4ynt5Ecr/wPLuLBTRlha6MVqjIsXzKLDqjlcP2oc+Fxb9qdOSfBLrCSkNSK5azD1z04rdZwizWjvdL", + "nKt7+UIrQtWqcmNKWXCvmn1xXn/KXDHCivtf/7c4YDn7X7YMJQtfXYbPvDIlstlslryI4O4zS9gIuUDb", + "Rht+Xq3bpolBljNHVqoh80aC2HWnmFSEQ7TemxeNQXiBeRz5lBmrDVqSAaMxL2vs9hTf6P4XLCisQKqB", + "3sTyViviUjkQcjBAi4ogggfehgNXG6MtoYD+BLyHgsChHaNlCSNJPjB2v/oeYsCOJWyM1gVHVxeXF5ee", + "L21QcSNZzt63rxJmOI3aBS0IMrqL91/u734H6YDXpCtOsuBlOYGKWzfiJQqQirQPsS7IXbDWk22J/1lE", + "7Y8RSp81bQJ90GJyjIRp83Ilna8vL0+Ul7OE3QRnXTYWQWUrBdaaGfC67MD8UX1VulGA1mobV5ZVdUnS", + "cEurXK2j/dtcZBfIF/aygbZVKjjxI6F+KE/nBj61WHJCsQMBvSC5Hw8r5o/Kwr/xc1YOYj/u7FP3I904", + "GOkGSINAXkIjaQRzxRcNVirg4KQalgjzoJJOMkuM296PSvTiWh68jaP3s2TNynPaNE3aFlBtS1SFFt9G", + "YcJkxYeYGTVcV/e2ObGc9SfkU3ZzgztQISeM8JkyU3Kptu/eJ2rp/yF9sMIO5eofpUWRekbSfqyP7sKN", + "5QVeyg8ac90EnIZKOl+l4aMb6boUwMuGT1zoD5sTRy+q+8mjLcyjjx3BgffJcrI1vu0xZEGtz6zzUPuA", + "z/SP1O6R+PvSd+qa2p+i9kwg0qFOv+Kk0VakhlteIaF12dTHOfO2hthh8o+FJBRcQR9B8QoF8AGhhU8a", + "oknXwU/w+0l/DiJLU+2BY/GQ/zllHrz2EMIS5h2wPOD3ko5kC8BPx6VqjmY46qZrrl5L+Cgyh87iwPmB", + "pIvjDvyCp96KxHmOTNtzc+Pwf4qk9ky+Pnj7lrDLsH3AweN77wJ1ePk6ZlFrF9i+cY7ZAcWxFKizytzs", + "aflsoDqDhRxIFGlcRRpie60l3GpVWKT1A4jfDJUmWBiD/gRohBAQaPfHBqGqHYHhzoGktouUMtwVCdxo", + "Ho/LyG6Dp4dlO93G6rsjcfruXIzeXF7tr/L+yHmzdpB4pR57v34MMvvemB3sxLLneetwfs9Uzo2kUbpy", + "pdxdwj8FgeWeXqAc+4lICbBItVUoYCz5/Bp0ozajgSWtXbNQCGM5Dc2vt/cZiJKttq5Zsu0K/OkNX9Ae", + "84+DU+VpreS2i/pH/xniDP1yb5BafafX8LwktIqTHOMPh7m/2bSiFd4N2kp7wXKyo4ent5dVs4SFf45C", + "C6pt6fsEkcmzLPzjdOEaPhyivZA640Z6FP4OAAD//0nTejA+HAAA", } // GetSwagger returns the content of the embedded swagger specification file @@ -996,16 +1431,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -1023,7 +1458,7 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } @@ -1037,12 +1472,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/internal/test/strict-server/echo/server.go b/internal/test/strict-server/echo/server.go index 5b7026809e..914f967268 100644 --- a/internal/test/strict-server/echo/server.go +++ b/internal/test/strict-server/echo/server.go @@ -1,10 +1,11 @@ -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=server.cfg.yaml ../strict-schema.yaml -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=types.cfg.yaml ../strict-schema.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=server.cfg.yaml ../strict-schema.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=types.cfg.yaml ../strict-schema.yaml package api import ( "context" + "encoding/json" "io" "mime/multipart" ) @@ -40,6 +41,30 @@ func (s StrictServer) MultipartExample(ctx context.Context, request MultipartExa }), nil } +func (s StrictServer) MultipartRelatedExample(ctx context.Context, request MultipartRelatedExampleRequestObject) (MultipartRelatedExampleResponseObject, error) { + return MultipartRelatedExample200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.Body.NextPart() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + w, err := writer.CreatePart(part.Header) + if err != nil { + return err + } + _, err = io.Copy(w, part) + if err != nil { + return err + } + if err = part.Close(); err != nil { + return err + } + } + }), nil +} + func (s StrictServer) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) { switch { case request.Body != nil: @@ -100,3 +125,28 @@ func (s StrictServer) HeadersExample(ctx context.Context, request HeadersExample func (s StrictServer) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) { return ReusableResponses200JSONResponse{ReusableresponseJSONResponse: ReusableresponseJSONResponse{Body: *request.Body}}, nil } + +func (s StrictServer) RequiredJSONBody(ctx context.Context, request RequiredJSONBodyRequestObject) (RequiredJSONBodyResponseObject, error) { + return RequiredJSONBody200JSONResponse(*request.Body), nil +} + +func (s StrictServer) RequiredTextBody(ctx context.Context, request RequiredTextBodyRequestObject) (RequiredTextBodyResponseObject, error) { + return RequiredTextBody200TextResponse(*request.Body), nil +} + +func (s StrictServer) ReservedGoKeywordParameters(ctx context.Context, request ReservedGoKeywordParametersRequestObject) (ReservedGoKeywordParametersResponseObject, error) { + return ReservedGoKeywordParameters200TextResponse(""), nil +} + +func (s StrictServer) UnionExample(ctx context.Context, request UnionExampleRequestObject) (UnionExampleResponseObject, error) { + union, err := json.Marshal(*request.Body) + if err != nil { + return nil, err + } + + return UnionExample200JSONResponse{ + Body: struct{ union json.RawMessage }{ + union: union, + }, + }, nil +} diff --git a/internal/test/strict-server/echo/types.cfg.yaml b/internal/test/strict-server/echo/types.cfg.yaml index 4ea1d8aa5b..2ed9740ea7 100644 --- a/internal/test/strict-server/echo/types.cfg.yaml +++ b/internal/test/strict-server/echo/types.cfg.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: api generate: models: true diff --git a/internal/test/strict-server/echo/types.gen.go b/internal/test/strict-server/echo/types.gen.go index 33827cb7a4..fac014c4bb 100644 --- a/internal/test/strict-server/echo/types.gen.go +++ b/internal/test/strict-server/echo/types.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api // Example defines model for example. @@ -14,6 +14,9 @@ type Reusableresponse = Example // MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. type MultipleRequestAndResponseTypesTextBody = string +// RequiredTextBodyTextBody defines parameters for RequiredTextBody. +type RequiredTextBodyTextBody = string + // TextExampleTextBody defines parameters for TextExample. type TextExampleTextBody = string @@ -29,6 +32,9 @@ type JSONExampleJSONRequestBody = Example // MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. type MultipartExampleMultipartRequestBody = Example +// MultipartRelatedExampleMultipartRequestBody defines body for MultipartRelatedExample for multipart/related ContentType. +type MultipartRelatedExampleMultipartRequestBody = Example + // MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. type MultipleRequestAndResponseTypesJSONRequestBody = Example @@ -41,6 +47,12 @@ type MultipleRequestAndResponseTypesMultipartRequestBody = Example // MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. type MultipleRequestAndResponseTypesTextRequestBody = MultipleRequestAndResponseTypesTextBody +// RequiredJSONBodyJSONRequestBody defines body for RequiredJSONBody for application/json ContentType. +type RequiredJSONBodyJSONRequestBody = Example + +// RequiredTextBodyTextRequestBody defines body for RequiredTextBody for text/plain ContentType. +type RequiredTextBodyTextRequestBody = RequiredTextBodyTextBody + // ReusableResponsesJSONRequestBody defines body for ReusableResponses for application/json ContentType. type ReusableResponsesJSONRequestBody = Example @@ -52,3 +64,6 @@ type URLEncodedExampleFormdataRequestBody = Example // HeadersExampleJSONRequestBody defines body for HeadersExample for application/json ContentType. type HeadersExampleJSONRequestBody = Example + +// UnionExampleJSONRequestBody defines body for UnionExample for application/json ContentType. +type UnionExampleJSONRequestBody = Example diff --git a/internal/test/strict-server/fiber/fiber_strict_test.go b/internal/test/strict-server/fiber/fiber_strict_test.go new file mode 100644 index 0000000000..cb8b509b08 --- /dev/null +++ b/internal/test/strict-server/fiber/fiber_strict_test.go @@ -0,0 +1,229 @@ +package api + +import ( + "bytes" + "encoding/json" + "io" + "mime" + "mime/multipart" + "net/http" + "net/url" + "strings" + "testing" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/adaptor" + "github.com/stretchr/testify/assert" + + clientAPI "github.com/oapi-codegen/oapi-codegen/v2/internal/test/strict-server/client" + "github.com/oapi-codegen/runtime" + "github.com/oapi-codegen/testutil" +) + +func TestFiberServer(t *testing.T) { + server := StrictServer{} + strictHandler := NewStrictHandler(server, nil) + r := fiber.New() + RegisterHandlers(r, strictHandler) + testImpl(t, adaptor.FiberApp(r)) +} + +func testImpl(t *testing.T, handler http.Handler) { + t.Run("JSONExample", func(t *testing.T) { + value := "123" + requestBody := clientAPI.Example{Value: &value} + rr := testutil.NewRequest().Post("/json").WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) + var responseBody clientAPI.Example + err := json.NewDecoder(rr.Body).Decode(&responseBody) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + }) + t.Run("URLEncodedExample", func(t *testing.T) { + value := "456" + requestBody := clientAPI.Example{Value: &value} + requestBodyEncoded, err := runtime.MarshalForm(&requestBody, nil) + assert.NoError(t, err) + rr := testutil.NewRequest().Post("/urlencoded").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(requestBodyEncoded.Encode())).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "application/x-www-form-urlencoded", rr.Header().Get("Content-Type")) + values, err := url.ParseQuery(rr.Body.String()) + assert.NoError(t, err) + var responseBody clientAPI.Example + err = runtime.BindForm(&responseBody, values, nil, nil) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + }) + t.Run("MultipartExample", func(t *testing.T) { + value := "789" + fieldName := "value" + var writer bytes.Buffer + mw := multipart.NewWriter(&writer) + field, err := mw.CreateFormField(fieldName) + assert.NoError(t, err) + _, _ = field.Write([]byte(value)) + assert.NoError(t, mw.Close()) + rr := testutil.NewRequest().Post("/multipart").WithContentType(mw.FormDataContentType()).WithBody(writer.Bytes()).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + contentType, params, err := mime.ParseMediaType(rr.Header().Get("Content-Type")) + assert.NoError(t, err) + assert.Equal(t, "multipart/form-data", contentType) + reader := multipart.NewReader(rr.Body, params["boundary"]) + part, err := reader.NextPart() + assert.NoError(t, err) + assert.Equal(t, part.FormName(), fieldName) + readValue, err := io.ReadAll(part) + assert.NoError(t, err) + assert.Equal(t, value, string(readValue)) + _, err = reader.NextPart() + assert.Equal(t, io.EOF, err) + }) + t.Run("MultipartRelatedExample", func(t *testing.T) { + value := "789" + fieldName := "value" + var writer bytes.Buffer + mw := multipart.NewWriter(&writer) + field, err := mw.CreateFormField(fieldName) + assert.NoError(t, err) + _, _ = field.Write([]byte(value)) + assert.NoError(t, mw.Close()) + rr := testutil.NewRequest().Post("/multipart-related").WithContentType(mime.FormatMediaType("multipart/related", map[string]string{"boundary": mw.Boundary()})).WithBody(writer.Bytes()).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + contentType, params, err := mime.ParseMediaType(rr.Header().Get("Content-Type")) + assert.NoError(t, err) + assert.Equal(t, "multipart/related", contentType) + reader := multipart.NewReader(rr.Body, params["boundary"]) + part, err := reader.NextPart() + assert.NoError(t, err) + assert.Equal(t, part.FormName(), fieldName) + readValue, err := io.ReadAll(part) + assert.NoError(t, err) + assert.Equal(t, value, string(readValue)) + _, err = reader.NextPart() + assert.Equal(t, io.EOF, err) + }) + t.Run("TextExample", func(t *testing.T) { + value := "text" + rr := testutil.NewRequest().Post("/text").WithContentType("text/plain").WithBody([]byte(value)).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "text/plain", rr.Header().Get("Content-Type")) + assert.Equal(t, value, rr.Body.String()) + }) + t.Run("UnknownExample", func(t *testing.T) { + data := []byte("unknown data") + rr := testutil.NewRequest().Post("/unknown").WithContentType("image/png").WithBody(data).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "video/mp4", rr.Header().Get("Content-Type")) + assert.Equal(t, data, rr.Body.Bytes()) + }) + t.Run("MultipleRequestAndResponseTypesJSON", func(t *testing.T) { + value := "123" + requestBody := clientAPI.Example{Value: &value} + rr := testutil.NewRequest().Post("/multiple").WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) + var responseBody clientAPI.Example + err := json.NewDecoder(rr.Body).Decode(&responseBody) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + }) + t.Run("MultipleRequestAndResponseTypesFormdata", func(t *testing.T) { + value := "456" + requestBody := clientAPI.Example{Value: &value} + requestBodyEncoded, err := runtime.MarshalForm(&requestBody, nil) + assert.NoError(t, err) + rr := testutil.NewRequest().Post("/multiple").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(requestBodyEncoded.Encode())).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "application/x-www-form-urlencoded", rr.Header().Get("Content-Type")) + values, err := url.ParseQuery(rr.Body.String()) + assert.NoError(t, err) + var responseBody clientAPI.Example + err = runtime.BindForm(&responseBody, values, nil, nil) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + }) + t.Run("MultipleRequestAndResponseTypesMultipart", func(t *testing.T) { + value := "789" + fieldName := "value" + var writer bytes.Buffer + mw := multipart.NewWriter(&writer) + field, err := mw.CreateFormField(fieldName) + assert.NoError(t, err) + _, _ = field.Write([]byte(value)) + assert.NoError(t, mw.Close()) + rr := testutil.NewRequest().Post("/multiple").WithContentType(mw.FormDataContentType()).WithBody(writer.Bytes()).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + contentType, params, err := mime.ParseMediaType(rr.Header().Get("Content-Type")) + assert.NoError(t, err) + assert.Equal(t, "multipart/form-data", contentType) + reader := multipart.NewReader(rr.Body, params["boundary"]) + part, err := reader.NextPart() + assert.NoError(t, err) + assert.Equal(t, part.FormName(), fieldName) + readValue, err := io.ReadAll(part) + assert.NoError(t, err) + assert.Equal(t, value, string(readValue)) + _, err = reader.NextPart() + assert.Equal(t, io.EOF, err) + }) + t.Run("MultipleRequestAndResponseTypesText", func(t *testing.T) { + value := "text" + rr := testutil.NewRequest().Post("/multiple").WithContentType("text/plain").WithBody([]byte(value)).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "text/plain", rr.Header().Get("Content-Type")) + assert.Equal(t, value, rr.Body.String()) + }) + t.Run("MultipleRequestAndResponseTypesImage", func(t *testing.T) { + data := []byte("unknown data") + rr := testutil.NewRequest().Post("/multiple").WithContentType("image/png").WithBody(data).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "image/png", rr.Header().Get("Content-Type")) + assert.Equal(t, data, rr.Body.Bytes()) + }) + t.Run("HeadersExample", func(t *testing.T) { + header1 := "value1" + header2 := "890" + value := "asdf" + requestBody := clientAPI.Example{Value: &value} + rr := testutil.NewRequest().Post("/with-headers").WithHeader("header1", header1).WithHeader("header2", header2).WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) + var responseBody clientAPI.Example + err := json.NewDecoder(rr.Body).Decode(&responseBody) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + assert.Equal(t, header1, rr.Header().Get("header1")) + assert.Equal(t, header2, rr.Header().Get("header2")) + }) + t.Run("UnspecifiedContentType", func(t *testing.T) { + data := []byte("image data") + contentType := "image/jpeg" + rr := testutil.NewRequest().Post("/unspecified-content-type").WithContentType(contentType).WithBody(data).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, contentType, rr.Header().Get("Content-Type")) + assert.Equal(t, data, rr.Body.Bytes()) + }) + t.Run("ReusableResponses", func(t *testing.T) { + value := "jkl;" + requestBody := clientAPI.Example{Value: &value} + rr := testutil.NewRequest().Post("/reusable-responses").WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) + var responseBody clientAPI.Example + err := json.NewDecoder(rr.Body).Decode(&responseBody) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + }) + t.Run("UnionResponses", func(t *testing.T) { + value := "union" + requestBody := clientAPI.Example{Value: &value} + rr := testutil.NewRequest().Post("/with-union").WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) + var responseBody clientAPI.Example + err := json.NewDecoder(rr.Body).Decode(&responseBody) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + }) +} diff --git a/internal/test/strict-server/fiber/server.cfg.yaml b/internal/test/strict-server/fiber/server.cfg.yaml new file mode 100644 index 0000000000..73540ffc2c --- /dev/null +++ b/internal/test/strict-server/fiber/server.cfg.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +generate: + fiber-server: true + strict-server: true + embedded-spec: true +output: server.gen.go diff --git a/internal/test/strict-server/fiber/server.gen.go b/internal/test/strict-server/fiber/server.gen.go new file mode 100644 index 0000000000..44d3c65de7 --- /dev/null +++ b/internal/test/strict-server/fiber/server.gen.go @@ -0,0 +1,1479 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "mime" + "mime/multipart" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/gofiber/fiber/v2" + "github.com/oapi-codegen/runtime" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (POST /json) + JSONExample(c *fiber.Ctx) error + + // (POST /multipart) + MultipartExample(c *fiber.Ctx) error + + // (POST /multipart-related) + MultipartRelatedExample(c *fiber.Ctx) error + + // (POST /multiple) + MultipleRequestAndResponseTypes(c *fiber.Ctx) error + + // (POST /required-json-body) + RequiredJSONBody(c *fiber.Ctx) error + + // (POST /required-text-body) + RequiredTextBody(c *fiber.Ctx) error + + // (GET /reserved-go-keyword-parameters/{type}) + ReservedGoKeywordParameters(c *fiber.Ctx, pType string) error + + // (POST /reusable-responses) + ReusableResponses(c *fiber.Ctx) error + + // (POST /text) + TextExample(c *fiber.Ctx) error + + // (POST /unknown) + UnknownExample(c *fiber.Ctx) error + + // (POST /unspecified-content-type) + UnspecifiedContentType(c *fiber.Ctx) error + + // (POST /urlencoded) + URLEncodedExample(c *fiber.Ctx) error + + // (POST /with-headers) + HeadersExample(c *fiber.Ctx, params HeadersExampleParams) error + + // (POST /with-union) + UnionExample(c *fiber.Ctx) error +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +type MiddlewareFunc fiber.Handler + +// JSONExample operation middleware +func (siw *ServerInterfaceWrapper) JSONExample(c *fiber.Ctx) error { + + return siw.Handler.JSONExample(c) +} + +// MultipartExample operation middleware +func (siw *ServerInterfaceWrapper) MultipartExample(c *fiber.Ctx) error { + + return siw.Handler.MultipartExample(c) +} + +// MultipartRelatedExample operation middleware +func (siw *ServerInterfaceWrapper) MultipartRelatedExample(c *fiber.Ctx) error { + + return siw.Handler.MultipartRelatedExample(c) +} + +// MultipleRequestAndResponseTypes operation middleware +func (siw *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(c *fiber.Ctx) error { + + return siw.Handler.MultipleRequestAndResponseTypes(c) +} + +// RequiredJSONBody operation middleware +func (siw *ServerInterfaceWrapper) RequiredJSONBody(c *fiber.Ctx) error { + + return siw.Handler.RequiredJSONBody(c) +} + +// RequiredTextBody operation middleware +func (siw *ServerInterfaceWrapper) RequiredTextBody(c *fiber.Ctx) error { + + return siw.Handler.RequiredTextBody(c) +} + +// ReservedGoKeywordParameters operation middleware +func (siw *ServerInterfaceWrapper) ReservedGoKeywordParameters(c *fiber.Ctx) error { + + var err error + + // ------------- Path parameter "type" ------------- + var pType string + + err = runtime.BindStyledParameterWithOptions("simple", "type", c.Params("type"), &pType, runtime.BindStyledParameterOptions{Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter type: %w", err).Error()) + } + + return siw.Handler.ReservedGoKeywordParameters(c, pType) +} + +// ReusableResponses operation middleware +func (siw *ServerInterfaceWrapper) ReusableResponses(c *fiber.Ctx) error { + + return siw.Handler.ReusableResponses(c) +} + +// TextExample operation middleware +func (siw *ServerInterfaceWrapper) TextExample(c *fiber.Ctx) error { + + return siw.Handler.TextExample(c) +} + +// UnknownExample operation middleware +func (siw *ServerInterfaceWrapper) UnknownExample(c *fiber.Ctx) error { + + return siw.Handler.UnknownExample(c) +} + +// UnspecifiedContentType operation middleware +func (siw *ServerInterfaceWrapper) UnspecifiedContentType(c *fiber.Ctx) error { + + return siw.Handler.UnspecifiedContentType(c) +} + +// URLEncodedExample operation middleware +func (siw *ServerInterfaceWrapper) URLEncodedExample(c *fiber.Ctx) error { + + return siw.Handler.URLEncodedExample(c) +} + +// HeadersExample operation middleware +func (siw *ServerInterfaceWrapper) HeadersExample(c *fiber.Ctx) error { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params HeadersExampleParams + + headers := c.GetReqHeaders() + + // ------------- Required header parameter "header1" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("header1")]; found { + var Header1 string + n := len(valueList) + if n != 1 { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Too many values for ParamName header1, 1 is required, but %d found", n)) + } + + err = runtime.BindStyledParameterWithOptions("simple", "header1", valueList[0], &Header1, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter header1: %w", err).Error()) + } + + params.Header1 = Header1 + + } else { + err = fmt.Errorf("Header parameter header1 is required, but not found: %w", err) + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + + // ------------- Optional header parameter "header2" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("header2")]; found { + var Header2 int + n := len(valueList) + if n != 1 { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Too many values for ParamName header2, 1 is required, but %d found", n)) + } + + err = runtime.BindStyledParameterWithOptions("simple", "header2", valueList[0], &Header2, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false, Type: "integer", Format: ""}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter header2: %w", err).Error()) + } + + params.Header2 = &Header2 + + } + + return siw.Handler.HeadersExample(c, params) +} + +// UnionExample operation middleware +func (siw *ServerInterfaceWrapper) UnionExample(c *fiber.Ctx) error { + + return siw.Handler.UnionExample(c) +} + +// FiberServerOptions provides options for the Fiber server. +type FiberServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router fiber.Router, si ServerInterface) { + RegisterHandlersWithOptions(router, si, FiberServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router fiber.Router, si ServerInterface, options FiberServerOptions) { + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + for _, m := range options.Middlewares { + router.Use(fiber.Handler(m)) + } + + router.Post(options.BaseURL+"/json", wrapper.JSONExample) + + router.Post(options.BaseURL+"/multipart", wrapper.MultipartExample) + + router.Post(options.BaseURL+"/multipart-related", wrapper.MultipartRelatedExample) + + router.Post(options.BaseURL+"/multiple", wrapper.MultipleRequestAndResponseTypes) + + router.Post(options.BaseURL+"/required-json-body", wrapper.RequiredJSONBody) + + router.Post(options.BaseURL+"/required-text-body", wrapper.RequiredTextBody) + + router.Get(options.BaseURL+"/reserved-go-keyword-parameters/:type", wrapper.ReservedGoKeywordParameters) + + router.Post(options.BaseURL+"/reusable-responses", wrapper.ReusableResponses) + + router.Post(options.BaseURL+"/text", wrapper.TextExample) + + router.Post(options.BaseURL+"/unknown", wrapper.UnknownExample) + + router.Post(options.BaseURL+"/unspecified-content-type", wrapper.UnspecifiedContentType) + + router.Post(options.BaseURL+"/urlencoded", wrapper.URLEncodedExample) + + router.Post(options.BaseURL+"/with-headers", wrapper.HeadersExample) + + router.Post(options.BaseURL+"/with-union", wrapper.UnionExample) + +} + +type BadrequestResponse struct { +} + +type ReusableresponseResponseHeaders struct { + Header1 string + Header2 int +} +type ReusableresponseJSONResponse struct { + Body Example + + Headers ReusableresponseResponseHeaders +} + +type JSONExampleRequestObject struct { + Body *JSONExampleJSONRequestBody +} + +type JSONExampleResponseObject interface { + VisitJSONExampleResponse(ctx *fiber.Ctx) error +} + +type JSONExample200JSONResponse Example + +func (response JSONExample200JSONResponse) VisitJSONExampleResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response) +} + +type JSONExample400Response = BadrequestResponse + +func (response JSONExample400Response) VisitJSONExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type JSONExampledefaultResponse struct { + StatusCode int +} + +func (response JSONExampledefaultResponse) VisitJSONExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(response.StatusCode) + return nil +} + +type MultipartExampleRequestObject struct { + Body *multipart.Reader +} + +type MultipartExampleResponseObject interface { + VisitMultipartExampleResponse(ctx *fiber.Ctx) error +} + +type MultipartExample200MultipartResponse func(writer *multipart.Writer) error + +func (response MultipartExample200MultipartResponse) VisitMultipartExampleResponse(ctx *fiber.Ctx) error { + writer := multipart.NewWriter(ctx.Response().BodyWriter()) + ctx.Response().Header.Set("Content-Type", writer.FormDataContentType()) + ctx.Status(200) + + defer writer.Close() + return response(writer) +} + +type MultipartExample400Response = BadrequestResponse + +func (response MultipartExample400Response) VisitMultipartExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type MultipartExampledefaultResponse struct { + StatusCode int +} + +func (response MultipartExampledefaultResponse) VisitMultipartExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(response.StatusCode) + return nil +} + +type MultipartRelatedExampleRequestObject struct { + Body *multipart.Reader +} + +type MultipartRelatedExampleResponseObject interface { + VisitMultipartRelatedExampleResponse(ctx *fiber.Ctx) error +} + +type MultipartRelatedExample200MultipartResponse func(writer *multipart.Writer) error + +func (response MultipartRelatedExample200MultipartResponse) VisitMultipartRelatedExampleResponse(ctx *fiber.Ctx) error { + writer := multipart.NewWriter(ctx.Response().BodyWriter()) + ctx.Response().Header.Set("Content-Type", mime.FormatMediaType("multipart/related", map[string]string{"boundary": writer.Boundary()})) + ctx.Status(200) + + defer writer.Close() + return response(writer) +} + +type MultipartRelatedExample400Response = BadrequestResponse + +func (response MultipartRelatedExample400Response) VisitMultipartRelatedExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type MultipartRelatedExampledefaultResponse struct { + StatusCode int +} + +func (response MultipartRelatedExampledefaultResponse) VisitMultipartRelatedExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(response.StatusCode) + return nil +} + +type MultipleRequestAndResponseTypesRequestObject struct { + JSONBody *MultipleRequestAndResponseTypesJSONRequestBody + FormdataBody *MultipleRequestAndResponseTypesFormdataRequestBody + Body io.Reader + MultipartBody *multipart.Reader + TextBody *MultipleRequestAndResponseTypesTextRequestBody +} + +type MultipleRequestAndResponseTypesResponseObject interface { + VisitMultipleRequestAndResponseTypesResponse(ctx *fiber.Ctx) error +} + +type MultipleRequestAndResponseTypes200JSONResponse Example + +func (response MultipleRequestAndResponseTypes200JSONResponse) VisitMultipleRequestAndResponseTypesResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response) +} + +type MultipleRequestAndResponseTypes200FormdataResponse Example + +func (response MultipleRequestAndResponseTypes200FormdataResponse) VisitMultipleRequestAndResponseTypesResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/x-www-form-urlencoded") + ctx.Status(200) + + if form, err := runtime.MarshalForm(response, nil); err != nil { + return err + } else { + _, err := ctx.WriteString(form.Encode()) + return err + } +} + +type MultipleRequestAndResponseTypes200ImagepngResponse struct { + Body io.Reader + ContentLength int64 +} + +func (response MultipleRequestAndResponseTypes200ImagepngResponse) VisitMultipleRequestAndResponseTypesResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "image/png") + if response.ContentLength != 0 { + ctx.Response().Header.Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + ctx.Status(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(ctx.Response().BodyWriter(), response.Body) + return err +} + +type MultipleRequestAndResponseTypes200MultipartResponse func(writer *multipart.Writer) error + +func (response MultipleRequestAndResponseTypes200MultipartResponse) VisitMultipleRequestAndResponseTypesResponse(ctx *fiber.Ctx) error { + writer := multipart.NewWriter(ctx.Response().BodyWriter()) + ctx.Response().Header.Set("Content-Type", writer.FormDataContentType()) + ctx.Status(200) + + defer writer.Close() + return response(writer) +} + +type MultipleRequestAndResponseTypes200TextResponse string + +func (response MultipleRequestAndResponseTypes200TextResponse) VisitMultipleRequestAndResponseTypesResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "text/plain") + ctx.Status(200) + + _, err := ctx.WriteString(string(response)) + return err +} + +type MultipleRequestAndResponseTypes400Response = BadrequestResponse + +func (response MultipleRequestAndResponseTypes400Response) VisitMultipleRequestAndResponseTypesResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type RequiredJSONBodyRequestObject struct { + Body *RequiredJSONBodyJSONRequestBody +} + +type RequiredJSONBodyResponseObject interface { + VisitRequiredJSONBodyResponse(ctx *fiber.Ctx) error +} + +type RequiredJSONBody200JSONResponse Example + +func (response RequiredJSONBody200JSONResponse) VisitRequiredJSONBodyResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response) +} + +type RequiredJSONBody400Response = BadrequestResponse + +func (response RequiredJSONBody400Response) VisitRequiredJSONBodyResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type RequiredJSONBodydefaultResponse struct { + StatusCode int +} + +func (response RequiredJSONBodydefaultResponse) VisitRequiredJSONBodyResponse(ctx *fiber.Ctx) error { + ctx.Status(response.StatusCode) + return nil +} + +type RequiredTextBodyRequestObject struct { + Body *RequiredTextBodyTextRequestBody +} + +type RequiredTextBodyResponseObject interface { + VisitRequiredTextBodyResponse(ctx *fiber.Ctx) error +} + +type RequiredTextBody200TextResponse string + +func (response RequiredTextBody200TextResponse) VisitRequiredTextBodyResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "text/plain") + ctx.Status(200) + + _, err := ctx.WriteString(string(response)) + return err +} + +type RequiredTextBody400Response = BadrequestResponse + +func (response RequiredTextBody400Response) VisitRequiredTextBodyResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type RequiredTextBodydefaultResponse struct { + StatusCode int +} + +func (response RequiredTextBodydefaultResponse) VisitRequiredTextBodyResponse(ctx *fiber.Ctx) error { + ctx.Status(response.StatusCode) + return nil +} + +type ReservedGoKeywordParametersRequestObject struct { + Type string `json:"type"` +} + +type ReservedGoKeywordParametersResponseObject interface { + VisitReservedGoKeywordParametersResponse(ctx *fiber.Ctx) error +} + +type ReservedGoKeywordParameters200TextResponse string + +func (response ReservedGoKeywordParameters200TextResponse) VisitReservedGoKeywordParametersResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "text/plain") + ctx.Status(200) + + _, err := ctx.WriteString(string(response)) + return err +} + +type ReusableResponsesRequestObject struct { + Body *ReusableResponsesJSONRequestBody +} + +type ReusableResponsesResponseObject interface { + VisitReusableResponsesResponse(ctx *fiber.Ctx) error +} + +type ReusableResponses200JSONResponse struct{ ReusableresponseJSONResponse } + +func (response ReusableResponses200JSONResponse) VisitReusableResponsesResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("header1", fmt.Sprint(response.Headers.Header1)) + ctx.Response().Header.Set("header2", fmt.Sprint(response.Headers.Header2)) + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response.Body) +} + +type ReusableResponses400Response = BadrequestResponse + +func (response ReusableResponses400Response) VisitReusableResponsesResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type ReusableResponsesdefaultResponse struct { + StatusCode int +} + +func (response ReusableResponsesdefaultResponse) VisitReusableResponsesResponse(ctx *fiber.Ctx) error { + ctx.Status(response.StatusCode) + return nil +} + +type TextExampleRequestObject struct { + Body *TextExampleTextRequestBody +} + +type TextExampleResponseObject interface { + VisitTextExampleResponse(ctx *fiber.Ctx) error +} + +type TextExample200TextResponse string + +func (response TextExample200TextResponse) VisitTextExampleResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "text/plain") + ctx.Status(200) + + _, err := ctx.WriteString(string(response)) + return err +} + +type TextExample400Response = BadrequestResponse + +func (response TextExample400Response) VisitTextExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type TextExampledefaultResponse struct { + StatusCode int +} + +func (response TextExampledefaultResponse) VisitTextExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(response.StatusCode) + return nil +} + +type UnknownExampleRequestObject struct { + Body io.Reader +} + +type UnknownExampleResponseObject interface { + VisitUnknownExampleResponse(ctx *fiber.Ctx) error +} + +type UnknownExample200Videomp4Response struct { + Body io.Reader + ContentLength int64 +} + +func (response UnknownExample200Videomp4Response) VisitUnknownExampleResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "video/mp4") + if response.ContentLength != 0 { + ctx.Response().Header.Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + ctx.Status(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(ctx.Response().BodyWriter(), response.Body) + return err +} + +type UnknownExample400Response = BadrequestResponse + +func (response UnknownExample400Response) VisitUnknownExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type UnknownExampledefaultResponse struct { + StatusCode int +} + +func (response UnknownExampledefaultResponse) VisitUnknownExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(response.StatusCode) + return nil +} + +type UnspecifiedContentTypeRequestObject struct { + ContentType string + Body io.Reader +} + +type UnspecifiedContentTypeResponseObject interface { + VisitUnspecifiedContentTypeResponse(ctx *fiber.Ctx) error +} + +type UnspecifiedContentType200VideoResponse struct { + Body io.Reader + ContentType string + ContentLength int64 +} + +func (response UnspecifiedContentType200VideoResponse) VisitUnspecifiedContentTypeResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", response.ContentType) + if response.ContentLength != 0 { + ctx.Response().Header.Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + ctx.Status(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(ctx.Response().BodyWriter(), response.Body) + return err +} + +type UnspecifiedContentType400Response = BadrequestResponse + +func (response UnspecifiedContentType400Response) VisitUnspecifiedContentTypeResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type UnspecifiedContentType401Response struct { +} + +func (response UnspecifiedContentType401Response) VisitUnspecifiedContentTypeResponse(ctx *fiber.Ctx) error { + ctx.Status(401) + return nil +} + +type UnspecifiedContentType403Response struct { +} + +func (response UnspecifiedContentType403Response) VisitUnspecifiedContentTypeResponse(ctx *fiber.Ctx) error { + ctx.Status(403) + return nil +} + +type UnspecifiedContentTypedefaultResponse struct { + StatusCode int +} + +func (response UnspecifiedContentTypedefaultResponse) VisitUnspecifiedContentTypeResponse(ctx *fiber.Ctx) error { + ctx.Status(response.StatusCode) + return nil +} + +type URLEncodedExampleRequestObject struct { + Body *URLEncodedExampleFormdataRequestBody +} + +type URLEncodedExampleResponseObject interface { + VisitURLEncodedExampleResponse(ctx *fiber.Ctx) error +} + +type URLEncodedExample200FormdataResponse Example + +func (response URLEncodedExample200FormdataResponse) VisitURLEncodedExampleResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/x-www-form-urlencoded") + ctx.Status(200) + + if form, err := runtime.MarshalForm(response, nil); err != nil { + return err + } else { + _, err := ctx.WriteString(form.Encode()) + return err + } +} + +type URLEncodedExample400Response = BadrequestResponse + +func (response URLEncodedExample400Response) VisitURLEncodedExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type URLEncodedExampledefaultResponse struct { + StatusCode int +} + +func (response URLEncodedExampledefaultResponse) VisitURLEncodedExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(response.StatusCode) + return nil +} + +type HeadersExampleRequestObject struct { + Params HeadersExampleParams + Body *HeadersExampleJSONRequestBody +} + +type HeadersExampleResponseObject interface { + VisitHeadersExampleResponse(ctx *fiber.Ctx) error +} + +type HeadersExample200ResponseHeaders struct { + Header1 string + Header2 int +} + +type HeadersExample200JSONResponse struct { + Body Example + Headers HeadersExample200ResponseHeaders +} + +func (response HeadersExample200JSONResponse) VisitHeadersExampleResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("header1", fmt.Sprint(response.Headers.Header1)) + ctx.Response().Header.Set("header2", fmt.Sprint(response.Headers.Header2)) + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response.Body) +} + +type HeadersExample400Response = BadrequestResponse + +func (response HeadersExample400Response) VisitHeadersExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type HeadersExampledefaultResponse struct { + StatusCode int +} + +func (response HeadersExampledefaultResponse) VisitHeadersExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(response.StatusCode) + return nil +} + +type UnionExampleRequestObject struct { + Body *UnionExampleJSONRequestBody +} + +type UnionExampleResponseObject interface { + VisitUnionExampleResponse(ctx *fiber.Ctx) error +} + +type UnionExample200ResponseHeaders struct { + Header1 string + Header2 int +} + +type UnionExample200ApplicationAlternativePlusJSONResponse struct { + Body Example + Headers UnionExample200ResponseHeaders +} + +func (response UnionExample200ApplicationAlternativePlusJSONResponse) VisitUnionExampleResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("header1", fmt.Sprint(response.Headers.Header1)) + ctx.Response().Header.Set("header2", fmt.Sprint(response.Headers.Header2)) + ctx.Response().Header.Set("Content-Type", "application/alternative+json") + ctx.Status(200) + + return ctx.JSON(&response.Body) +} + +type UnionExample200JSONResponse struct { + Body struct { + union json.RawMessage + } + Headers UnionExample200ResponseHeaders +} + +func (response UnionExample200JSONResponse) VisitUnionExampleResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("header1", fmt.Sprint(response.Headers.Header1)) + ctx.Response().Header.Set("header2", fmt.Sprint(response.Headers.Header2)) + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response.Body.union) +} + +type UnionExample400Response = BadrequestResponse + +func (response UnionExample400Response) VisitUnionExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type UnionExampledefaultResponse struct { + StatusCode int +} + +func (response UnionExampledefaultResponse) VisitUnionExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(response.StatusCode) + return nil +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (POST /json) + JSONExample(ctx context.Context, request JSONExampleRequestObject) (JSONExampleResponseObject, error) + + // (POST /multipart) + MultipartExample(ctx context.Context, request MultipartExampleRequestObject) (MultipartExampleResponseObject, error) + + // (POST /multipart-related) + MultipartRelatedExample(ctx context.Context, request MultipartRelatedExampleRequestObject) (MultipartRelatedExampleResponseObject, error) + + // (POST /multiple) + MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) + + // (POST /required-json-body) + RequiredJSONBody(ctx context.Context, request RequiredJSONBodyRequestObject) (RequiredJSONBodyResponseObject, error) + + // (POST /required-text-body) + RequiredTextBody(ctx context.Context, request RequiredTextBodyRequestObject) (RequiredTextBodyResponseObject, error) + + // (GET /reserved-go-keyword-parameters/{type}) + ReservedGoKeywordParameters(ctx context.Context, request ReservedGoKeywordParametersRequestObject) (ReservedGoKeywordParametersResponseObject, error) + + // (POST /reusable-responses) + ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) + + // (POST /text) + TextExample(ctx context.Context, request TextExampleRequestObject) (TextExampleResponseObject, error) + + // (POST /unknown) + UnknownExample(ctx context.Context, request UnknownExampleRequestObject) (UnknownExampleResponseObject, error) + + // (POST /unspecified-content-type) + UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) (UnspecifiedContentTypeResponseObject, error) + + // (POST /urlencoded) + URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) (URLEncodedExampleResponseObject, error) + + // (POST /with-headers) + HeadersExample(ctx context.Context, request HeadersExampleRequestObject) (HeadersExampleResponseObject, error) + + // (POST /with-union) + UnionExample(ctx context.Context, request UnionExampleRequestObject) (UnionExampleResponseObject, error) +} + +type StrictHandlerFunc func(ctx *fiber.Ctx, args interface{}) (interface{}, error) + +type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +// JSONExample operation middleware +func (sh *strictHandler) JSONExample(ctx *fiber.Ctx) error { + var request JSONExampleRequestObject + + var body JSONExampleJSONRequestBody + if err := ctx.BodyParser(&body); err != nil { + if !errors.Is(err, io.EOF) { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else { + request.Body = &body + } + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.JSONExample(ctx.UserContext(), request.(JSONExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "JSONExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(JSONExampleResponseObject); ok { + if err := validResponse.VisitJSONExampleResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// MultipartExample operation middleware +func (sh *strictHandler) MultipartExample(ctx *fiber.Ctx) error { + var request MultipartExampleRequestObject + + request.Body = multipart.NewReader(bytes.NewReader(ctx.Request().Body()), string(ctx.Request().Header.MultipartFormBoundary())) + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.MultipartExample(ctx.UserContext(), request.(MultipartExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipartExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(MultipartExampleResponseObject); ok { + if err := validResponse.VisitMultipartExampleResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// MultipartRelatedExample operation middleware +func (sh *strictHandler) MultipartRelatedExample(ctx *fiber.Ctx) error { + var request MultipartRelatedExampleRequestObject + + if _, params, err := mime.ParseMediaType(string(ctx.Request().Header.ContentType())); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if boundary := params["boundary"]; boundary == "" { + return fiber.NewError(fiber.StatusBadRequest, http.ErrMissingBoundary.Error()) + } else { + request.Body = multipart.NewReader(bytes.NewReader(ctx.Request().Body()), boundary) + } + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.MultipartRelatedExample(ctx.UserContext(), request.(MultipartRelatedExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipartRelatedExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(MultipartRelatedExampleResponseObject); ok { + if err := validResponse.VisitMultipartRelatedExampleResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// MultipleRequestAndResponseTypes operation middleware +func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx *fiber.Ctx) error { + var request MultipleRequestAndResponseTypesRequestObject + + if strings.HasPrefix(string(ctx.Request().Header.ContentType()), "application/json") { + + var body MultipleRequestAndResponseTypesJSONRequestBody + if err := ctx.BodyParser(&body); err != nil { + if !errors.Is(err, io.EOF) { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else { + request.JSONBody = &body + } + } + if strings.HasPrefix(string(ctx.Request().Header.ContentType()), "application/x-www-form-urlencoded") { + var body MultipleRequestAndResponseTypesFormdataRequestBody + if err := ctx.BodyParser(&body); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + request.FormdataBody = &body + } + if strings.HasPrefix(string(ctx.Request().Header.ContentType()), "image/png") { + request.Body = bytes.NewReader(ctx.Request().Body()) + } + if strings.HasPrefix(string(ctx.Request().Header.ContentType()), "multipart/form-data") { + request.MultipartBody = multipart.NewReader(bytes.NewReader(ctx.Request().Body()), string(ctx.Request().Header.MultipartFormBoundary())) + } + if strings.HasPrefix(string(ctx.Request().Header.ContentType()), "text/plain") { + data := ctx.Request().Body() + if len(data) > 0 { + body := MultipleRequestAndResponseTypesTextRequestBody(data) + request.TextBody = &body + } + } + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.MultipleRequestAndResponseTypes(ctx.UserContext(), request.(MultipleRequestAndResponseTypesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipleRequestAndResponseTypes") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(MultipleRequestAndResponseTypesResponseObject); ok { + if err := validResponse.VisitMultipleRequestAndResponseTypesResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// RequiredJSONBody operation middleware +func (sh *strictHandler) RequiredJSONBody(ctx *fiber.Ctx) error { + var request RequiredJSONBodyRequestObject + + var body RequiredJSONBodyJSONRequestBody + if err := ctx.BodyParser(&body); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + request.Body = &body + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.RequiredJSONBody(ctx.UserContext(), request.(RequiredJSONBodyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "RequiredJSONBody") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(RequiredJSONBodyResponseObject); ok { + if err := validResponse.VisitRequiredJSONBodyResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// RequiredTextBody operation middleware +func (sh *strictHandler) RequiredTextBody(ctx *fiber.Ctx) error { + var request RequiredTextBodyRequestObject + + data := ctx.Request().Body() + body := RequiredTextBodyTextRequestBody(data) + request.Body = &body + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.RequiredTextBody(ctx.UserContext(), request.(RequiredTextBodyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "RequiredTextBody") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(RequiredTextBodyResponseObject); ok { + if err := validResponse.VisitRequiredTextBodyResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// ReservedGoKeywordParameters operation middleware +func (sh *strictHandler) ReservedGoKeywordParameters(ctx *fiber.Ctx, pType string) error { + var request ReservedGoKeywordParametersRequestObject + + request.Type = pType + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.ReservedGoKeywordParameters(ctx.UserContext(), request.(ReservedGoKeywordParametersRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReservedGoKeywordParameters") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(ReservedGoKeywordParametersResponseObject); ok { + if err := validResponse.VisitReservedGoKeywordParametersResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// ReusableResponses operation middleware +func (sh *strictHandler) ReusableResponses(ctx *fiber.Ctx) error { + var request ReusableResponsesRequestObject + + var body ReusableResponsesJSONRequestBody + if err := ctx.BodyParser(&body); err != nil { + if !errors.Is(err, io.EOF) { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else { + request.Body = &body + } + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.ReusableResponses(ctx.UserContext(), request.(ReusableResponsesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReusableResponses") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(ReusableResponsesResponseObject); ok { + if err := validResponse.VisitReusableResponsesResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// TextExample operation middleware +func (sh *strictHandler) TextExample(ctx *fiber.Ctx) error { + var request TextExampleRequestObject + + data := ctx.Request().Body() + if len(data) > 0 { + body := TextExampleTextRequestBody(data) + request.Body = &body + } + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.TextExample(ctx.UserContext(), request.(TextExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "TextExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(TextExampleResponseObject); ok { + if err := validResponse.VisitTextExampleResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// UnknownExample operation middleware +func (sh *strictHandler) UnknownExample(ctx *fiber.Ctx) error { + var request UnknownExampleRequestObject + + request.Body = bytes.NewReader(ctx.Request().Body()) + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.UnknownExample(ctx.UserContext(), request.(UnknownExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnknownExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(UnknownExampleResponseObject); ok { + if err := validResponse.VisitUnknownExampleResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// UnspecifiedContentType operation middleware +func (sh *strictHandler) UnspecifiedContentType(ctx *fiber.Ctx) error { + var request UnspecifiedContentTypeRequestObject + + request.ContentType = string(ctx.Request().Header.ContentType()) + + request.Body = bytes.NewReader(ctx.Request().Body()) + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.UnspecifiedContentType(ctx.UserContext(), request.(UnspecifiedContentTypeRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnspecifiedContentType") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(UnspecifiedContentTypeResponseObject); ok { + if err := validResponse.VisitUnspecifiedContentTypeResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// URLEncodedExample operation middleware +func (sh *strictHandler) URLEncodedExample(ctx *fiber.Ctx) error { + var request URLEncodedExampleRequestObject + + var body URLEncodedExampleFormdataRequestBody + if err := ctx.BodyParser(&body); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + request.Body = &body + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.URLEncodedExample(ctx.UserContext(), request.(URLEncodedExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "URLEncodedExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(URLEncodedExampleResponseObject); ok { + if err := validResponse.VisitURLEncodedExampleResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// HeadersExample operation middleware +func (sh *strictHandler) HeadersExample(ctx *fiber.Ctx, params HeadersExampleParams) error { + var request HeadersExampleRequestObject + + request.Params = params + + var body HeadersExampleJSONRequestBody + if err := ctx.BodyParser(&body); err != nil { + if !errors.Is(err, io.EOF) { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else { + request.Body = &body + } + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.HeadersExample(ctx.UserContext(), request.(HeadersExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "HeadersExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(HeadersExampleResponseObject); ok { + if err := validResponse.VisitHeadersExampleResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// UnionExample operation middleware +func (sh *strictHandler) UnionExample(ctx *fiber.Ctx) error { + var request UnionExampleRequestObject + + var body UnionExampleJSONRequestBody + if err := ctx.BodyParser(&body); err != nil { + if !errors.Is(err, io.EOF) { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else { + request.Body = &body + } + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.UnionExample(ctx.UserContext(), request.(UnionExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnionExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(UnionExampleResponseObject); ok { + if err := validResponse.VisitUnionExampleResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/+xZTXPbNhP+Kzt431NKmrbjE2+NJ5O2aeuObJ86PkDESkJCAiiwFK3R6L93QEBfFq1K", + "qT4ynt5Ecr/wPLuLBTRlha6MVqjIsXzKLDqjlcP2oc+Fxb9qdOSfBLrCSkNSK5azD1z04rdZwizWjvdL", + "nKt7+UIrQtWqcmNKWXCvmn1xXn/KXDHCivtf/7c4YDn7X7YMJQtfXYbPvDIlstlslryI4O4zS9gIuUDb", + "Rht+Xq3bpolBljNHVqoh80aC2HWnmFSEQ7TemxeNQXiBeRz5lBmrDVqSAaMxL2vs9hTf6P4XLCisQKqB", + "3sTyViviUjkQcjBAi4ogggfehgNXG6MtoYD+BLyHgsChHaNlCSNJPjB2v/oeYsCOJWyM1gVHVxeXF5ee", + "L21QcSNZzt63rxJmOI3aBS0IMrqL91/u734H6YDXpCtOsuBlOYGKWzfiJQqQirQPsS7IXbDWk22J/1lE", + "7Y8RSp81bQJ90GJyjIRp83Ilna8vL0+Ul7OE3QRnXTYWQWUrBdaaGfC67MD8UX1VulGA1mobV5ZVdUnS", + "cEurXK2j/dtcZBfIF/aygbZVKjjxI6F+KE/nBj61WHJCsQMBvSC5Hw8r5o/Kwr/xc1YOYj/u7FP3I904", + "GOkGSINAXkIjaQRzxRcNVirg4KQalgjzoJJOMkuM296PSvTiWh68jaP3s2TNynPaNE3aFlBtS1SFFt9G", + "YcJkxYeYGTVcV/e2ObGc9SfkU3ZzgztQISeM8JkyU3Kptu/eJ2rp/yF9sMIO5eofpUWRekbSfqyP7sKN", + "5QVeyg8ac90EnIZKOl+l4aMb6boUwMuGT1zoD5sTRy+q+8mjLcyjjx3BgffJcrI1vu0xZEGtz6zzUPuA", + "z/SP1O6R+PvSd+qa2p+i9kwg0qFOv+Kk0VakhlteIaF12dTHOfO2hthh8o+FJBRcQR9B8QoF8AGhhU8a", + "oknXwU/w+0l/DiJLU+2BY/GQ/zllHrz2EMIS5h2wPOD3ko5kC8BPx6VqjmY46qZrrl5L+Cgyh87iwPmB", + "pIvjDvyCp96KxHmOTNtzc+Pwf4qk9ky+Pnj7lrDLsH3AweN77wJ1ePk6ZlFrF9i+cY7ZAcWxFKizytzs", + "aflsoDqDhRxIFGlcRRpie60l3GpVWKT1A4jfDJUmWBiD/gRohBAQaPfHBqGqHYHhzoGktouUMtwVCdxo", + "Ho/LyG6Dp4dlO93G6rsjcfruXIzeXF7tr/L+yHmzdpB4pR57v34MMvvemB3sxLLneetwfs9Uzo2kUbpy", + "pdxdwj8FgeWeXqAc+4lICbBItVUoYCz5/Bp0ozajgSWtXbNQCGM5Dc2vt/cZiJKttq5Zsu0K/OkNX9Ae", + "84+DU+VpreS2i/pH/xniDP1yb5BafafX8LwktIqTHOMPh7m/2bSiFd4N2kp7wXKyo4ent5dVs4SFf45C", + "C6pt6fsEkcmzLPzjdOEaPhyivZA640Z6FP4OAAD//0nTejA+HAAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/strict-server/fiber/server.go b/internal/test/strict-server/fiber/server.go new file mode 100644 index 0000000000..914f967268 --- /dev/null +++ b/internal/test/strict-server/fiber/server.go @@ -0,0 +1,152 @@ +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=server.cfg.yaml ../strict-schema.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=types.cfg.yaml ../strict-schema.yaml + +package api + +import ( + "context" + "encoding/json" + "io" + "mime/multipart" +) + +type StrictServer struct { +} + +func (s StrictServer) JSONExample(ctx context.Context, request JSONExampleRequestObject) (JSONExampleResponseObject, error) { + return JSONExample200JSONResponse(*request.Body), nil +} + +func (s StrictServer) MultipartExample(ctx context.Context, request MultipartExampleRequestObject) (MultipartExampleResponseObject, error) { + return MultipartExample200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.Body.NextPart() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + w, err := writer.CreatePart(part.Header) + if err != nil { + return err + } + _, err = io.Copy(w, part) + if err != nil { + return err + } + if err = part.Close(); err != nil { + return err + } + } + }), nil +} + +func (s StrictServer) MultipartRelatedExample(ctx context.Context, request MultipartRelatedExampleRequestObject) (MultipartRelatedExampleResponseObject, error) { + return MultipartRelatedExample200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.Body.NextPart() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + w, err := writer.CreatePart(part.Header) + if err != nil { + return err + } + _, err = io.Copy(w, part) + if err != nil { + return err + } + if err = part.Close(); err != nil { + return err + } + } + }), nil +} + +func (s StrictServer) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) { + switch { + case request.Body != nil: + return MultipleRequestAndResponseTypes200ImagepngResponse{Body: request.Body}, nil + case request.JSONBody != nil: + return MultipleRequestAndResponseTypes200JSONResponse(*request.JSONBody), nil + case request.FormdataBody != nil: + return MultipleRequestAndResponseTypes200FormdataResponse(*request.FormdataBody), nil + case request.TextBody != nil: + return MultipleRequestAndResponseTypes200TextResponse(*request.TextBody), nil + case request.MultipartBody != nil: + return MultipleRequestAndResponseTypes200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.MultipartBody.NextPart() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + w, err := writer.CreatePart(part.Header) + if err != nil { + return err + } + _, err = io.Copy(w, part) + if err != nil { + return err + } + if err = part.Close(); err != nil { + return err + } + } + }), nil + default: + return MultipleRequestAndResponseTypes400Response{}, nil + } +} + +func (s StrictServer) TextExample(ctx context.Context, request TextExampleRequestObject) (TextExampleResponseObject, error) { + return TextExample200TextResponse(*request.Body), nil +} + +func (s StrictServer) UnknownExample(ctx context.Context, request UnknownExampleRequestObject) (UnknownExampleResponseObject, error) { + return UnknownExample200Videomp4Response{Body: request.Body}, nil +} + +func (s StrictServer) UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) (UnspecifiedContentTypeResponseObject, error) { + return UnspecifiedContentType200VideoResponse{Body: request.Body, ContentType: request.ContentType}, nil +} + +func (s StrictServer) URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) (URLEncodedExampleResponseObject, error) { + return URLEncodedExample200FormdataResponse(*request.Body), nil +} + +func (s StrictServer) HeadersExample(ctx context.Context, request HeadersExampleRequestObject) (HeadersExampleResponseObject, error) { + return HeadersExample200JSONResponse{Body: *request.Body, Headers: HeadersExample200ResponseHeaders{Header1: request.Params.Header1, Header2: *request.Params.Header2}}, nil +} + +func (s StrictServer) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) { + return ReusableResponses200JSONResponse{ReusableresponseJSONResponse: ReusableresponseJSONResponse{Body: *request.Body}}, nil +} + +func (s StrictServer) RequiredJSONBody(ctx context.Context, request RequiredJSONBodyRequestObject) (RequiredJSONBodyResponseObject, error) { + return RequiredJSONBody200JSONResponse(*request.Body), nil +} + +func (s StrictServer) RequiredTextBody(ctx context.Context, request RequiredTextBodyRequestObject) (RequiredTextBodyResponseObject, error) { + return RequiredTextBody200TextResponse(*request.Body), nil +} + +func (s StrictServer) ReservedGoKeywordParameters(ctx context.Context, request ReservedGoKeywordParametersRequestObject) (ReservedGoKeywordParametersResponseObject, error) { + return ReservedGoKeywordParameters200TextResponse(""), nil +} + +func (s StrictServer) UnionExample(ctx context.Context, request UnionExampleRequestObject) (UnionExampleResponseObject, error) { + union, err := json.Marshal(*request.Body) + if err != nil { + return nil, err + } + + return UnionExample200JSONResponse{ + Body: struct{ union json.RawMessage }{ + union: union, + }, + }, nil +} diff --git a/internal/test/strict-server/fiber/types.cfg.yaml b/internal/test/strict-server/fiber/types.cfg.yaml new file mode 100644 index 0000000000..2ed9740ea7 --- /dev/null +++ b/internal/test/strict-server/fiber/types.cfg.yaml @@ -0,0 +1,5 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +generate: + models: true +output: types.gen.go diff --git a/internal/test/strict-server/fiber/types.gen.go b/internal/test/strict-server/fiber/types.gen.go new file mode 100644 index 0000000000..fac014c4bb --- /dev/null +++ b/internal/test/strict-server/fiber/types.gen.go @@ -0,0 +1,69 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +// Example defines model for example. +type Example struct { + Value *string `json:"value,omitempty"` +} + +// Reusableresponse defines model for reusableresponse. +type Reusableresponse = Example + +// MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesTextBody = string + +// RequiredTextBodyTextBody defines parameters for RequiredTextBody. +type RequiredTextBodyTextBody = string + +// TextExampleTextBody defines parameters for TextExample. +type TextExampleTextBody = string + +// HeadersExampleParams defines parameters for HeadersExample. +type HeadersExampleParams struct { + Header1 string `json:"header1"` + Header2 *int `json:"header2,omitempty"` +} + +// JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. +type JSONExampleJSONRequestBody = Example + +// MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. +type MultipartExampleMultipartRequestBody = Example + +// MultipartRelatedExampleMultipartRequestBody defines body for MultipartRelatedExample for multipart/related ContentType. +type MultipartRelatedExampleMultipartRequestBody = Example + +// MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. +type MultipleRequestAndResponseTypesJSONRequestBody = Example + +// MultipleRequestAndResponseTypesFormdataRequestBody defines body for MultipleRequestAndResponseTypes for application/x-www-form-urlencoded ContentType. +type MultipleRequestAndResponseTypesFormdataRequestBody = Example + +// MultipleRequestAndResponseTypesMultipartRequestBody defines body for MultipleRequestAndResponseTypes for multipart/form-data ContentType. +type MultipleRequestAndResponseTypesMultipartRequestBody = Example + +// MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. +type MultipleRequestAndResponseTypesTextRequestBody = MultipleRequestAndResponseTypesTextBody + +// RequiredJSONBodyJSONRequestBody defines body for RequiredJSONBody for application/json ContentType. +type RequiredJSONBodyJSONRequestBody = Example + +// RequiredTextBodyTextRequestBody defines body for RequiredTextBody for text/plain ContentType. +type RequiredTextBodyTextRequestBody = RequiredTextBodyTextBody + +// ReusableResponsesJSONRequestBody defines body for ReusableResponses for application/json ContentType. +type ReusableResponsesJSONRequestBody = Example + +// TextExampleTextRequestBody defines body for TextExample for text/plain ContentType. +type TextExampleTextRequestBody = TextExampleTextBody + +// URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. +type URLEncodedExampleFormdataRequestBody = Example + +// HeadersExampleJSONRequestBody defines body for HeadersExample for application/json ContentType. +type HeadersExampleJSONRequestBody = Example + +// UnionExampleJSONRequestBody defines body for UnionExample for application/json ContentType. +type UnionExampleJSONRequestBody = Example diff --git a/internal/test/strict-server/gin/server.cfg.yaml b/internal/test/strict-server/gin/server.cfg.yaml index 66ffd5795f..a79bf0e85d 100644 --- a/internal/test/strict-server/gin/server.cfg.yaml +++ b/internal/test/strict-server/gin/server.cfg.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: api generate: gin-server: true diff --git a/internal/test/strict-server/gin/server.gen.go b/internal/test/strict-server/gin/server.gen.go index 81b9aef138..b664de93d3 100644 --- a/internal/test/strict-server/gin/server.gen.go +++ b/internal/test/strict-server/gin/server.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api import ( @@ -9,17 +9,20 @@ import ( "context" "encoding/base64" "encoding/json" + "errors" "fmt" "io" + "mime" "mime/multipart" "net/http" "net/url" "path" "strings" - "github.com/deepmap/oapi-codegen/pkg/runtime" "github.com/getkin/kin-openapi/openapi3" "github.com/gin-gonic/gin" + "github.com/oapi-codegen/runtime" + strictgin "github.com/oapi-codegen/runtime/strictmiddleware/gin" ) // ServerInterface represents all server handlers. @@ -31,9 +34,21 @@ type ServerInterface interface { // (POST /multipart) MultipartExample(c *gin.Context) + // (POST /multipart-related) + MultipartRelatedExample(c *gin.Context) + // (POST /multiple) MultipleRequestAndResponseTypes(c *gin.Context) + // (POST /required-json-body) + RequiredJSONBody(c *gin.Context) + + // (POST /required-text-body) + RequiredTextBody(c *gin.Context) + + // (GET /reserved-go-keyword-parameters/{type}) + ReservedGoKeywordParameters(c *gin.Context, pType string) + // (POST /reusable-responses) ReusableResponses(c *gin.Context) @@ -51,6 +66,9 @@ type ServerInterface interface { // (POST /with-headers) HeadersExample(c *gin.Context, params HeadersExampleParams) + + // (POST /with-union) + UnionExample(c *gin.Context) } // ServerInterfaceWrapper converts contexts to parameters. @@ -88,6 +106,19 @@ func (siw *ServerInterfaceWrapper) MultipartExample(c *gin.Context) { siw.Handler.MultipartExample(c) } +// MultipartRelatedExample operation middleware +func (siw *ServerInterfaceWrapper) MultipartRelatedExample(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.MultipartRelatedExample(c) +} + // MultipleRequestAndResponseTypes operation middleware func (siw *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(c *gin.Context) { @@ -101,6 +132,56 @@ func (siw *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(c *gin.Contex siw.Handler.MultipleRequestAndResponseTypes(c) } +// RequiredJSONBody operation middleware +func (siw *ServerInterfaceWrapper) RequiredJSONBody(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.RequiredJSONBody(c) +} + +// RequiredTextBody operation middleware +func (siw *ServerInterfaceWrapper) RequiredTextBody(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.RequiredTextBody(c) +} + +// ReservedGoKeywordParameters operation middleware +func (siw *ServerInterfaceWrapper) ReservedGoKeywordParameters(c *gin.Context) { + + var err error + + // ------------- Path parameter "type" ------------- + var pType string + + err = runtime.BindStyledParameterWithOptions("simple", "type", c.Param("type"), &pType, runtime.BindStyledParameterOptions{Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter type: %w", err), http.StatusBadRequest) + return + } + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.ReservedGoKeywordParameters(c, pType) +} + // ReusableResponses operation middleware func (siw *ServerInterfaceWrapper) ReusableResponses(c *gin.Context) { @@ -185,16 +266,16 @@ func (siw *ServerInterfaceWrapper) HeadersExample(c *gin.Context) { return } - err = runtime.BindStyledParameterWithLocation("simple", false, "header1", runtime.ParamLocationHeader, valueList[0], &Header1) + err = runtime.BindStyledParameterWithOptions("simple", "header1", valueList[0], &Header1, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: true, Type: "string", Format: ""}) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter header1: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter header1: %w", err), http.StatusBadRequest) return } params.Header1 = Header1 } else { - siw.ErrorHandler(c, fmt.Errorf("Header parameter header1 is required, but not found: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Header parameter header1 is required, but not found"), http.StatusBadRequest) return } @@ -207,9 +288,9 @@ func (siw *ServerInterfaceWrapper) HeadersExample(c *gin.Context) { return } - err = runtime.BindStyledParameterWithLocation("simple", false, "header2", runtime.ParamLocationHeader, valueList[0], &Header2) + err = runtime.BindStyledParameterWithOptions("simple", "header2", valueList[0], &Header2, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false, Type: "integer", Format: ""}) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter header2: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter header2: %w", err), http.StatusBadRequest) return } @@ -227,6 +308,19 @@ func (siw *ServerInterfaceWrapper) HeadersExample(c *gin.Context) { siw.Handler.HeadersExample(c, params) } +// UnionExample operation middleware +func (siw *ServerInterfaceWrapper) UnionExample(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.UnionExample(c) +} + // GinServerOptions provides options for the Gin server. type GinServerOptions struct { BaseURL string @@ -256,13 +350,18 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options router.POST(options.BaseURL+"/json", wrapper.JSONExample) router.POST(options.BaseURL+"/multipart", wrapper.MultipartExample) + router.POST(options.BaseURL+"/multipart-related", wrapper.MultipartRelatedExample) router.POST(options.BaseURL+"/multiple", wrapper.MultipleRequestAndResponseTypes) + router.POST(options.BaseURL+"/required-json-body", wrapper.RequiredJSONBody) + router.POST(options.BaseURL+"/required-text-body", wrapper.RequiredTextBody) + router.GET(options.BaseURL+"/reserved-go-keyword-parameters/:type", wrapper.ReservedGoKeywordParameters) router.POST(options.BaseURL+"/reusable-responses", wrapper.ReusableResponses) router.POST(options.BaseURL+"/text", wrapper.TextExample) router.POST(options.BaseURL+"/unknown", wrapper.UnknownExample) router.POST(options.BaseURL+"/unspecified-content-type", wrapper.UnspecifiedContentType) router.POST(options.BaseURL+"/urlencoded", wrapper.URLEncodedExample) router.POST(options.BaseURL+"/with-headers", wrapper.HeadersExample) + router.POST(options.BaseURL+"/with-union", wrapper.UnionExample) } type BadrequestResponse struct { @@ -346,6 +445,41 @@ func (response MultipartExampledefaultResponse) VisitMultipartExampleResponse(w return nil } +type MultipartRelatedExampleRequestObject struct { + Body *multipart.Reader +} + +type MultipartRelatedExampleResponseObject interface { + VisitMultipartRelatedExampleResponse(w http.ResponseWriter) error +} + +type MultipartRelatedExample200MultipartResponse func(writer *multipart.Writer) error + +func (response MultipartRelatedExample200MultipartResponse) VisitMultipartRelatedExampleResponse(w http.ResponseWriter) error { + writer := multipart.NewWriter(w) + w.Header().Set("Content-Type", mime.FormatMediaType("multipart/related", map[string]string{"boundary": writer.Boundary()})) + w.WriteHeader(200) + + defer writer.Close() + return response(writer) +} + +type MultipartRelatedExample400Response = BadrequestResponse + +func (response MultipartRelatedExample400Response) VisitMultipartRelatedExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type MultipartRelatedExampledefaultResponse struct { + StatusCode int +} + +func (response MultipartRelatedExampledefaultResponse) VisitMultipartRelatedExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + type MultipleRequestAndResponseTypesRequestObject struct { JSONBody *MultipleRequestAndResponseTypesJSONRequestBody FormdataBody *MultipleRequestAndResponseTypesFormdataRequestBody @@ -428,6 +562,91 @@ func (response MultipleRequestAndResponseTypes400Response) VisitMultipleRequestA return nil } +type RequiredJSONBodyRequestObject struct { + Body *RequiredJSONBodyJSONRequestBody +} + +type RequiredJSONBodyResponseObject interface { + VisitRequiredJSONBodyResponse(w http.ResponseWriter) error +} + +type RequiredJSONBody200JSONResponse Example + +func (response RequiredJSONBody200JSONResponse) VisitRequiredJSONBodyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type RequiredJSONBody400Response = BadrequestResponse + +func (response RequiredJSONBody400Response) VisitRequiredJSONBodyResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type RequiredJSONBodydefaultResponse struct { + StatusCode int +} + +func (response RequiredJSONBodydefaultResponse) VisitRequiredJSONBodyResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type RequiredTextBodyRequestObject struct { + Body *RequiredTextBodyTextRequestBody +} + +type RequiredTextBodyResponseObject interface { + VisitRequiredTextBodyResponse(w http.ResponseWriter) error +} + +type RequiredTextBody200TextResponse string + +func (response RequiredTextBody200TextResponse) VisitRequiredTextBodyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(200) + + _, err := w.Write([]byte(response)) + return err +} + +type RequiredTextBody400Response = BadrequestResponse + +func (response RequiredTextBody400Response) VisitRequiredTextBodyResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type RequiredTextBodydefaultResponse struct { + StatusCode int +} + +func (response RequiredTextBodydefaultResponse) VisitRequiredTextBodyResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type ReservedGoKeywordParametersRequestObject struct { + Type string `json:"type"` +} + +type ReservedGoKeywordParametersResponseObject interface { + VisitReservedGoKeywordParametersResponse(w http.ResponseWriter) error +} + +type ReservedGoKeywordParameters200TextResponse string + +func (response ReservedGoKeywordParameters200TextResponse) VisitReservedGoKeywordParametersResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(200) + + _, err := w.Write([]byte(response)) + return err +} + type ReusableResponsesRequestObject struct { Body *ReusableResponsesJSONRequestBody } @@ -439,9 +658,9 @@ type ReusableResponsesResponseObject interface { type ReusableResponses200JSONResponse struct{ ReusableresponseJSONResponse } func (response ReusableResponses200JSONResponse) VisitReusableResponsesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) w.Header().Set("header2", fmt.Sprint(response.Headers.Header2)) - w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response.Body) @@ -659,9 +878,9 @@ type HeadersExample200JSONResponse struct { } func (response HeadersExample200JSONResponse) VisitHeadersExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) w.Header().Set("header2", fmt.Sprint(response.Headers.Header2)) - w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response.Body) @@ -683,6 +902,65 @@ func (response HeadersExampledefaultResponse) VisitHeadersExampleResponse(w http return nil } +type UnionExampleRequestObject struct { + Body *UnionExampleJSONRequestBody +} + +type UnionExampleResponseObject interface { + VisitUnionExampleResponse(w http.ResponseWriter) error +} + +type UnionExample200ResponseHeaders struct { + Header1 string + Header2 int +} + +type UnionExample200ApplicationAlternativePlusJSONResponse struct { + Body Example + Headers UnionExample200ResponseHeaders +} + +func (response UnionExample200ApplicationAlternativePlusJSONResponse) VisitUnionExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/alternative+json") + w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) + w.Header().Set("header2", fmt.Sprint(response.Headers.Header2)) + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response.Body) +} + +type UnionExample200JSONResponse struct { + Body struct { + union json.RawMessage + } + Headers UnionExample200ResponseHeaders +} + +func (response UnionExample200JSONResponse) VisitUnionExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) + w.Header().Set("header2", fmt.Sprint(response.Headers.Header2)) + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response.Body.union) +} + +type UnionExample400Response = BadrequestResponse + +func (response UnionExample400Response) VisitUnionExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type UnionExampledefaultResponse struct { + StatusCode int +} + +func (response UnionExampledefaultResponse) VisitUnionExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + // StrictServerInterface represents all server handlers. type StrictServerInterface interface { @@ -692,9 +970,21 @@ type StrictServerInterface interface { // (POST /multipart) MultipartExample(ctx context.Context, request MultipartExampleRequestObject) (MultipartExampleResponseObject, error) + // (POST /multipart-related) + MultipartRelatedExample(ctx context.Context, request MultipartRelatedExampleRequestObject) (MultipartRelatedExampleResponseObject, error) + // (POST /multiple) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) + // (POST /required-json-body) + RequiredJSONBody(ctx context.Context, request RequiredJSONBodyRequestObject) (RequiredJSONBodyResponseObject, error) + + // (POST /required-text-body) + RequiredTextBody(ctx context.Context, request RequiredTextBodyRequestObject) (RequiredTextBodyResponseObject, error) + + // (GET /reserved-go-keyword-parameters/{type}) + ReservedGoKeywordParameters(ctx context.Context, request ReservedGoKeywordParametersRequestObject) (ReservedGoKeywordParametersResponseObject, error) + // (POST /reusable-responses) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) @@ -712,11 +1002,13 @@ type StrictServerInterface interface { // (POST /with-headers) HeadersExample(ctx context.Context, request HeadersExampleRequestObject) (HeadersExampleResponseObject, error) -} -type StrictHandlerFunc func(ctx *gin.Context, args interface{}) (interface{}, error) + // (POST /with-union) + UnionExample(ctx context.Context, request UnionExampleRequestObject) (UnionExampleResponseObject, error) +} -type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc +type StrictHandlerFunc = strictgin.StrictGinHandlerFunc +type StrictMiddlewareFunc = strictgin.StrictGinMiddlewareFunc func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { return &strictHandler{ssi: ssi, middlewares: middlewares} @@ -732,12 +1024,15 @@ func (sh *strictHandler) JSONExample(ctx *gin.Context) { var request JSONExampleRequestObject var body JSONExampleJSONRequestBody - if err := ctx.ShouldBind(&body); err != nil { - ctx.Status(http.StatusBadRequest) - ctx.Error(err) - return + if err := ctx.ShouldBindJSON(&body); err != nil { + if !errors.Is(err, io.EOF) { + ctx.Status(http.StatusBadRequest) + ctx.Error(err) + return + } + } else { + request.Body = &body } - request.Body = &body handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { return sh.ssi.JSONExample(ctx, request.(JSONExampleRequestObject)) @@ -750,12 +1045,13 @@ func (sh *strictHandler) JSONExample(ctx *gin.Context) { if err != nil { ctx.Error(err) + ctx.Status(http.StatusInternalServerError) } else if validResponse, ok := response.(JSONExampleResponseObject); ok { if err := validResponse.VisitJSONExampleResponse(ctx.Writer); err != nil { ctx.Error(err) } } else if response != nil { - ctx.Error(fmt.Errorf("Unexpected response type: %T", response)) + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) } } @@ -781,12 +1077,48 @@ func (sh *strictHandler) MultipartExample(ctx *gin.Context) { if err != nil { ctx.Error(err) + ctx.Status(http.StatusInternalServerError) } else if validResponse, ok := response.(MultipartExampleResponseObject); ok { if err := validResponse.VisitMultipartExampleResponse(ctx.Writer); err != nil { ctx.Error(err) } } else if response != nil { - ctx.Error(fmt.Errorf("Unexpected response type: %T", response)) + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) + } +} + +// MultipartRelatedExample operation middleware +func (sh *strictHandler) MultipartRelatedExample(ctx *gin.Context) { + var request MultipartRelatedExampleRequestObject + + if _, params, err := mime.ParseMediaType(ctx.Request.Header.Get("Content-Type")); err != nil { + ctx.Error(err) + return + } else if boundary := params["boundary"]; boundary == "" { + ctx.Error(http.ErrMissingBoundary) + return + } else { + request.Body = multipart.NewReader(ctx.Request.Body, boundary) + } + + handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { + return sh.ssi.MultipartRelatedExample(ctx, request.(MultipartRelatedExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipartRelatedExample") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.Error(err) + ctx.Status(http.StatusInternalServerError) + } else if validResponse, ok := response.(MultipartRelatedExampleResponseObject); ok { + if err := validResponse.VisitMultipartRelatedExampleResponse(ctx.Writer); err != nil { + ctx.Error(err) + } + } else if response != nil { + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) } } @@ -795,13 +1127,17 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx *gin.Context) { var request MultipleRequestAndResponseTypesRequestObject if strings.HasPrefix(ctx.GetHeader("Content-Type"), "application/json") { + var body MultipleRequestAndResponseTypesJSONRequestBody - if err := ctx.ShouldBind(&body); err != nil { - ctx.Status(http.StatusBadRequest) - ctx.Error(err) - return + if err := ctx.ShouldBindJSON(&body); err != nil { + if !errors.Is(err, io.EOF) { + ctx.Status(http.StatusBadRequest) + ctx.Error(err) + return + } + } else { + request.JSONBody = &body } - request.JSONBody = &body } if strings.HasPrefix(ctx.GetHeader("Content-Type"), "application/x-www-form-urlencoded") { if err := ctx.Request.ParseForm(); err != nil { @@ -832,8 +1168,10 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx *gin.Context) { ctx.Error(err) return } - body := MultipleRequestAndResponseTypesTextRequestBody(data) - request.TextBody = &body + if len(data) > 0 { + body := MultipleRequestAndResponseTypesTextRequestBody(data) + request.TextBody = &body + } } handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { @@ -847,27 +1185,124 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx *gin.Context) { if err != nil { ctx.Error(err) + ctx.Status(http.StatusInternalServerError) } else if validResponse, ok := response.(MultipleRequestAndResponseTypesResponseObject); ok { if err := validResponse.VisitMultipleRequestAndResponseTypesResponse(ctx.Writer); err != nil { ctx.Error(err) } } else if response != nil { - ctx.Error(fmt.Errorf("Unexpected response type: %T", response)) + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) } } -// ReusableResponses operation middleware -func (sh *strictHandler) ReusableResponses(ctx *gin.Context) { - var request ReusableResponsesRequestObject +// RequiredJSONBody operation middleware +func (sh *strictHandler) RequiredJSONBody(ctx *gin.Context) { + var request RequiredJSONBodyRequestObject - var body ReusableResponsesJSONRequestBody - if err := ctx.ShouldBind(&body); err != nil { + var body RequiredJSONBodyJSONRequestBody + if err := ctx.ShouldBindJSON(&body); err != nil { ctx.Status(http.StatusBadRequest) ctx.Error(err) return } request.Body = &body + handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { + return sh.ssi.RequiredJSONBody(ctx, request.(RequiredJSONBodyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "RequiredJSONBody") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.Error(err) + ctx.Status(http.StatusInternalServerError) + } else if validResponse, ok := response.(RequiredJSONBodyResponseObject); ok { + if err := validResponse.VisitRequiredJSONBodyResponse(ctx.Writer); err != nil { + ctx.Error(err) + } + } else if response != nil { + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) + } +} + +// RequiredTextBody operation middleware +func (sh *strictHandler) RequiredTextBody(ctx *gin.Context) { + var request RequiredTextBodyRequestObject + + data, err := io.ReadAll(ctx.Request.Body) + if err != nil { + ctx.Error(err) + return + } + body := RequiredTextBodyTextRequestBody(data) + request.Body = &body + + handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { + return sh.ssi.RequiredTextBody(ctx, request.(RequiredTextBodyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "RequiredTextBody") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.Error(err) + ctx.Status(http.StatusInternalServerError) + } else if validResponse, ok := response.(RequiredTextBodyResponseObject); ok { + if err := validResponse.VisitRequiredTextBodyResponse(ctx.Writer); err != nil { + ctx.Error(err) + } + } else if response != nil { + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) + } +} + +// ReservedGoKeywordParameters operation middleware +func (sh *strictHandler) ReservedGoKeywordParameters(ctx *gin.Context, pType string) { + var request ReservedGoKeywordParametersRequestObject + + request.Type = pType + + handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { + return sh.ssi.ReservedGoKeywordParameters(ctx, request.(ReservedGoKeywordParametersRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReservedGoKeywordParameters") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.Error(err) + ctx.Status(http.StatusInternalServerError) + } else if validResponse, ok := response.(ReservedGoKeywordParametersResponseObject); ok { + if err := validResponse.VisitReservedGoKeywordParametersResponse(ctx.Writer); err != nil { + ctx.Error(err) + } + } else if response != nil { + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) + } +} + +// ReusableResponses operation middleware +func (sh *strictHandler) ReusableResponses(ctx *gin.Context) { + var request ReusableResponsesRequestObject + + var body ReusableResponsesJSONRequestBody + if err := ctx.ShouldBindJSON(&body); err != nil { + if !errors.Is(err, io.EOF) { + ctx.Status(http.StatusBadRequest) + ctx.Error(err) + return + } + } else { + request.Body = &body + } + handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { return sh.ssi.ReusableResponses(ctx, request.(ReusableResponsesRequestObject)) } @@ -879,12 +1314,13 @@ func (sh *strictHandler) ReusableResponses(ctx *gin.Context) { if err != nil { ctx.Error(err) + ctx.Status(http.StatusInternalServerError) } else if validResponse, ok := response.(ReusableResponsesResponseObject); ok { if err := validResponse.VisitReusableResponsesResponse(ctx.Writer); err != nil { ctx.Error(err) } } else if response != nil { - ctx.Error(fmt.Errorf("Unexpected response type: %T", response)) + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) } } @@ -897,8 +1333,10 @@ func (sh *strictHandler) TextExample(ctx *gin.Context) { ctx.Error(err) return } - body := TextExampleTextRequestBody(data) - request.Body = &body + if len(data) > 0 { + body := TextExampleTextRequestBody(data) + request.Body = &body + } handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { return sh.ssi.TextExample(ctx, request.(TextExampleRequestObject)) @@ -911,12 +1349,13 @@ func (sh *strictHandler) TextExample(ctx *gin.Context) { if err != nil { ctx.Error(err) + ctx.Status(http.StatusInternalServerError) } else if validResponse, ok := response.(TextExampleResponseObject); ok { if err := validResponse.VisitTextExampleResponse(ctx.Writer); err != nil { ctx.Error(err) } } else if response != nil { - ctx.Error(fmt.Errorf("Unexpected response type: %T", response)) + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) } } @@ -937,12 +1376,13 @@ func (sh *strictHandler) UnknownExample(ctx *gin.Context) { if err != nil { ctx.Error(err) + ctx.Status(http.StatusInternalServerError) } else if validResponse, ok := response.(UnknownExampleResponseObject); ok { if err := validResponse.VisitUnknownExampleResponse(ctx.Writer); err != nil { ctx.Error(err) } } else if response != nil { - ctx.Error(fmt.Errorf("Unexpected response type: %T", response)) + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) } } @@ -965,12 +1405,13 @@ func (sh *strictHandler) UnspecifiedContentType(ctx *gin.Context) { if err != nil { ctx.Error(err) + ctx.Status(http.StatusInternalServerError) } else if validResponse, ok := response.(UnspecifiedContentTypeResponseObject); ok { if err := validResponse.VisitUnspecifiedContentTypeResponse(ctx.Writer); err != nil { ctx.Error(err) } } else if response != nil { - ctx.Error(fmt.Errorf("Unexpected response type: %T", response)) + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) } } @@ -1000,12 +1441,13 @@ func (sh *strictHandler) URLEncodedExample(ctx *gin.Context) { if err != nil { ctx.Error(err) + ctx.Status(http.StatusInternalServerError) } else if validResponse, ok := response.(URLEncodedExampleResponseObject); ok { if err := validResponse.VisitURLEncodedExampleResponse(ctx.Writer); err != nil { ctx.Error(err) } } else if response != nil { - ctx.Error(fmt.Errorf("Unexpected response type: %T", response)) + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) } } @@ -1016,12 +1458,15 @@ func (sh *strictHandler) HeadersExample(ctx *gin.Context, params HeadersExampleP request.Params = params var body HeadersExampleJSONRequestBody - if err := ctx.ShouldBind(&body); err != nil { - ctx.Status(http.StatusBadRequest) - ctx.Error(err) - return + if err := ctx.ShouldBindJSON(&body); err != nil { + if !errors.Is(err, io.EOF) { + ctx.Status(http.StatusBadRequest) + ctx.Error(err) + return + } + } else { + request.Body = &body } - request.Body = &body handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { return sh.ssi.HeadersExample(ctx, request.(HeadersExampleRequestObject)) @@ -1034,33 +1479,74 @@ func (sh *strictHandler) HeadersExample(ctx *gin.Context, params HeadersExampleP if err != nil { ctx.Error(err) + ctx.Status(http.StatusInternalServerError) } else if validResponse, ok := response.(HeadersExampleResponseObject); ok { if err := validResponse.VisitHeadersExampleResponse(ctx.Writer); err != nil { ctx.Error(err) } } else if response != nil { - ctx.Error(fmt.Errorf("Unexpected response type: %T", response)) + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) + } +} + +// UnionExample operation middleware +func (sh *strictHandler) UnionExample(ctx *gin.Context) { + var request UnionExampleRequestObject + + var body UnionExampleJSONRequestBody + if err := ctx.ShouldBindJSON(&body); err != nil { + if !errors.Is(err, io.EOF) { + ctx.Status(http.StatusBadRequest) + ctx.Error(err) + return + } + } else { + request.Body = &body + } + + handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { + return sh.ssi.UnionExample(ctx, request.(UnionExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnionExample") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.Error(err) + ctx.Status(http.StatusInternalServerError) + } else if validResponse, ok := response.(UnionExampleResponseObject); ok { + if err := validResponse.VisitUnionExampleResponse(ctx.Writer); err != nil { + ctx.Error(err) + } + } else if response != nil { + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) } } // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xYS4/iOBD+K1btnkaB0D194rbTGmnfI9Ezp9UcirgAzya2166QRoj/vnJsaBjSCFo8", - "pNXeEqde/qq+KsdLKExljSbNHoZLcOSt0Z7alzFKR//U5Dm8SfKFU5aV0TCEDyhH6dsqA0e1x3FJa/Ug", - "XxjNpFtVtLZUBQbV/JsP+kvwxYwqDE8/OprAEH7IX0LJ41ef0zNWtiRYrVbZdxF8+g0ymBFKcm208fFu", - "1zYvLMEQPDulpxCMRLH7TjGlmabkgrcgmoIIAus4hkuwzlhyrCJGcyxr6vaUVsz4GxUcd6D0xOxj+Wg0", - "o9JeSDWZkCPNIoEngg0vfG2tcUxSjBcieChYeHJzcpABKw6BwdP2ukgBe8hgTs5HR3f9QX8Q8mUsabQK", - "hvC+XcrAIs/aDW0SZE1X3n99+vSnUF5gzaZCVgWW5UJU6PwMy5KkUJpNiLEu2PehdeXazP8ik/rHhGUo", - "m7aCPhi5uETFtIW5Vc/3g8GVCnOVwUN01mVjE1S+xbDWzATrsgP0L/pvbRotyDnj0s7yqi5ZWXS8naxd", - "tP9YixwD+cZePjGu6klkvBDq5/J0U+BTM+gkydPMNF7MTCPYCElYikbxTKwVv2O30gKFV3paklgHlXVm", - "sqTUc3/ScpT28jnYuDiXsh0rz72maXpt8mpXki6MJPk2s6rCKeVWT3fVg21kGMJ4waFs97vrmYooA6Zn", - "zm2JSh8eHVdqJ/8jfTZiR7quzya9neR1E3dNKi8K1GIc+DjxgcRdvvZIOkqeRlsStxlxhzHaO61do2uG", - "5L8+qT7T81FD6oxkvXY1ngpYHRdfxyxpHQPbG7l/BIpzJcnklX040fLNQPWWCjVRJHtpF70Y22st4dHo", - "whHvDu1wAtaGxcZYOJjzjEREIBPeiIZEVXsWFr0XitsuUqp4uJe01zy+vET2GD2FyX5EVt9dKKfvbpXR", - "h8Hd6SrvL1w3O8P3FT6Ofv8YZU79wznblD/xjHI+vzeiczhW97buALop/HMUeJnpBak5SYFaCkdcO01S", - "zBWuf1v3uJkMvKTVosOKuPX61xLCCEkXC5CBxoo273epCJQLyLKrKTt0PXHQ1j1kh+4svv6Hf6gvedNz", - "6TpdZRAvZWKx1K4MGWW2wzyPlzl93+B0Sq6vTI5Wwerr6t8AAAD//ygSomqZEwAA", + "H4sIAAAAAAAC/+xZTXPbNhP+Kzt431NKmrbjE2+NJ5O2aeuObJ86PkDESkJCAiiwFK3R6L93QEBfFq1K", + "qT4ynt5Ecr/wPLuLBTRlha6MVqjIsXzKLDqjlcP2oc+Fxb9qdOSfBLrCSkNSK5azD1z04rdZwizWjvdL", + "nKt7+UIrQtWqcmNKWXCvmn1xXn/KXDHCivtf/7c4YDn7X7YMJQtfXYbPvDIlstlslryI4O4zS9gIuUDb", + "Rht+Xq3bpolBljNHVqoh80aC2HWnmFSEQ7TemxeNQXiBeRz5lBmrDVqSAaMxL2vs9hTf6P4XLCisQKqB", + "3sTyViviUjkQcjBAi4ogggfehgNXG6MtoYD+BLyHgsChHaNlCSNJPjB2v/oeYsCOJWyM1gVHVxeXF5ee", + "L21QcSNZzt63rxJmOI3aBS0IMrqL91/u734H6YDXpCtOsuBlOYGKWzfiJQqQirQPsS7IXbDWk22J/1lE", + "7Y8RSp81bQJ90GJyjIRp83Ilna8vL0+Ul7OE3QRnXTYWQWUrBdaaGfC67MD8UX1VulGA1mobV5ZVdUnS", + "cEurXK2j/dtcZBfIF/aygbZVKjjxI6F+KE/nBj61WHJCsQMBvSC5Hw8r5o/Kwr/xc1YOYj/u7FP3I904", + "GOkGSINAXkIjaQRzxRcNVirg4KQalgjzoJJOMkuM296PSvTiWh68jaP3s2TNynPaNE3aFlBtS1SFFt9G", + "YcJkxYeYGTVcV/e2ObGc9SfkU3ZzgztQISeM8JkyU3Kptu/eJ2rp/yF9sMIO5eofpUWRekbSfqyP7sKN", + "5QVeyg8ac90EnIZKOl+l4aMb6boUwMuGT1zoD5sTRy+q+8mjLcyjjx3BgffJcrI1vu0xZEGtz6zzUPuA", + "z/SP1O6R+PvSd+qa2p+i9kwg0qFOv+Kk0VakhlteIaF12dTHOfO2hthh8o+FJBRcQR9B8QoF8AGhhU8a", + "oknXwU/w+0l/DiJLU+2BY/GQ/zllHrz2EMIS5h2wPOD3ko5kC8BPx6VqjmY46qZrrl5L+Cgyh87iwPmB", + "pIvjDvyCp96KxHmOTNtzc+Pwf4qk9ky+Pnj7lrDLsH3AweN77wJ1ePk6ZlFrF9i+cY7ZAcWxFKizytzs", + "aflsoDqDhRxIFGlcRRpie60l3GpVWKT1A4jfDJUmWBiD/gRohBAQaPfHBqGqHYHhzoGktouUMtwVCdxo", + "Ho/LyG6Dp4dlO93G6rsjcfruXIzeXF7tr/L+yHmzdpB4pR57v34MMvvemB3sxLLneetwfs9Uzo2kUbpy", + "pdxdwj8FgeWeXqAc+4lICbBItVUoYCz5/Bp0ozajgSWtXbNQCGM5Dc2vt/cZiJKttq5Zsu0K/OkNX9Ae", + "84+DU+VpreS2i/pH/xniDP1yb5BafafX8LwktIqTHOMPh7m/2bSiFd4N2kp7wXKyo4ent5dVs4SFf45C", + "C6pt6fsEkcmzLPzjdOEaPhyivZA640Z6FP4OAAD//0nTejA+HAAA", } // GetSwagger returns the content of the embedded swagger specification file @@ -1068,16 +1554,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -1095,7 +1581,7 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } @@ -1109,12 +1595,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/internal/test/strict-server/gin/server.go b/internal/test/strict-server/gin/server.go index 5b7026809e..914f967268 100644 --- a/internal/test/strict-server/gin/server.go +++ b/internal/test/strict-server/gin/server.go @@ -1,10 +1,11 @@ -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=server.cfg.yaml ../strict-schema.yaml -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=types.cfg.yaml ../strict-schema.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=server.cfg.yaml ../strict-schema.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=types.cfg.yaml ../strict-schema.yaml package api import ( "context" + "encoding/json" "io" "mime/multipart" ) @@ -40,6 +41,30 @@ func (s StrictServer) MultipartExample(ctx context.Context, request MultipartExa }), nil } +func (s StrictServer) MultipartRelatedExample(ctx context.Context, request MultipartRelatedExampleRequestObject) (MultipartRelatedExampleResponseObject, error) { + return MultipartRelatedExample200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.Body.NextPart() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + w, err := writer.CreatePart(part.Header) + if err != nil { + return err + } + _, err = io.Copy(w, part) + if err != nil { + return err + } + if err = part.Close(); err != nil { + return err + } + } + }), nil +} + func (s StrictServer) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) { switch { case request.Body != nil: @@ -100,3 +125,28 @@ func (s StrictServer) HeadersExample(ctx context.Context, request HeadersExample func (s StrictServer) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) { return ReusableResponses200JSONResponse{ReusableresponseJSONResponse: ReusableresponseJSONResponse{Body: *request.Body}}, nil } + +func (s StrictServer) RequiredJSONBody(ctx context.Context, request RequiredJSONBodyRequestObject) (RequiredJSONBodyResponseObject, error) { + return RequiredJSONBody200JSONResponse(*request.Body), nil +} + +func (s StrictServer) RequiredTextBody(ctx context.Context, request RequiredTextBodyRequestObject) (RequiredTextBodyResponseObject, error) { + return RequiredTextBody200TextResponse(*request.Body), nil +} + +func (s StrictServer) ReservedGoKeywordParameters(ctx context.Context, request ReservedGoKeywordParametersRequestObject) (ReservedGoKeywordParametersResponseObject, error) { + return ReservedGoKeywordParameters200TextResponse(""), nil +} + +func (s StrictServer) UnionExample(ctx context.Context, request UnionExampleRequestObject) (UnionExampleResponseObject, error) { + union, err := json.Marshal(*request.Body) + if err != nil { + return nil, err + } + + return UnionExample200JSONResponse{ + Body: struct{ union json.RawMessage }{ + union: union, + }, + }, nil +} diff --git a/internal/test/strict-server/gin/types.cfg.yaml b/internal/test/strict-server/gin/types.cfg.yaml index 4ea1d8aa5b..2ed9740ea7 100644 --- a/internal/test/strict-server/gin/types.cfg.yaml +++ b/internal/test/strict-server/gin/types.cfg.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json package: api generate: models: true diff --git a/internal/test/strict-server/gin/types.gen.go b/internal/test/strict-server/gin/types.gen.go index 33827cb7a4..fac014c4bb 100644 --- a/internal/test/strict-server/gin/types.gen.go +++ b/internal/test/strict-server/gin/types.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api // Example defines model for example. @@ -14,6 +14,9 @@ type Reusableresponse = Example // MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. type MultipleRequestAndResponseTypesTextBody = string +// RequiredTextBodyTextBody defines parameters for RequiredTextBody. +type RequiredTextBodyTextBody = string + // TextExampleTextBody defines parameters for TextExample. type TextExampleTextBody = string @@ -29,6 +32,9 @@ type JSONExampleJSONRequestBody = Example // MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. type MultipartExampleMultipartRequestBody = Example +// MultipartRelatedExampleMultipartRequestBody defines body for MultipartRelatedExample for multipart/related ContentType. +type MultipartRelatedExampleMultipartRequestBody = Example + // MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. type MultipleRequestAndResponseTypesJSONRequestBody = Example @@ -41,6 +47,12 @@ type MultipleRequestAndResponseTypesMultipartRequestBody = Example // MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. type MultipleRequestAndResponseTypesTextRequestBody = MultipleRequestAndResponseTypesTextBody +// RequiredJSONBodyJSONRequestBody defines body for RequiredJSONBody for application/json ContentType. +type RequiredJSONBodyJSONRequestBody = Example + +// RequiredTextBodyTextRequestBody defines body for RequiredTextBody for text/plain ContentType. +type RequiredTextBodyTextRequestBody = RequiredTextBodyTextBody + // ReusableResponsesJSONRequestBody defines body for ReusableResponses for application/json ContentType. type ReusableResponsesJSONRequestBody = Example @@ -52,3 +64,6 @@ type URLEncodedExampleFormdataRequestBody = Example // HeadersExampleJSONRequestBody defines body for HeadersExample for application/json ContentType. type HeadersExampleJSONRequestBody = Example + +// UnionExampleJSONRequestBody defines body for UnionExample for application/json ContentType. +type UnionExampleJSONRequestBody = Example diff --git a/internal/test/strict-server/gorilla/server.cfg.yaml b/internal/test/strict-server/gorilla/server.cfg.yaml new file mode 100644 index 0000000000..a39491cbef --- /dev/null +++ b/internal/test/strict-server/gorilla/server.cfg.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +generate: + gorilla-server: true + strict-server: true + embedded-spec: true +output: server.gen.go diff --git a/internal/test/strict-server/gorilla/server.gen.go b/internal/test/strict-server/gorilla/server.gen.go new file mode 100644 index 0000000000..a141223fc5 --- /dev/null +++ b/internal/test/strict-server/gorilla/server.gen.go @@ -0,0 +1,1735 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "mime" + "mime/multipart" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/gorilla/mux" + "github.com/oapi-codegen/runtime" + strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (POST /json) + JSONExample(w http.ResponseWriter, r *http.Request) + + // (POST /multipart) + MultipartExample(w http.ResponseWriter, r *http.Request) + + // (POST /multipart-related) + MultipartRelatedExample(w http.ResponseWriter, r *http.Request) + + // (POST /multiple) + MultipleRequestAndResponseTypes(w http.ResponseWriter, r *http.Request) + + // (POST /required-json-body) + RequiredJSONBody(w http.ResponseWriter, r *http.Request) + + // (POST /required-text-body) + RequiredTextBody(w http.ResponseWriter, r *http.Request) + + // (GET /reserved-go-keyword-parameters/{type}) + ReservedGoKeywordParameters(w http.ResponseWriter, r *http.Request, pType string) + + // (POST /reusable-responses) + ReusableResponses(w http.ResponseWriter, r *http.Request) + + // (POST /text) + TextExample(w http.ResponseWriter, r *http.Request) + + // (POST /unknown) + UnknownExample(w http.ResponseWriter, r *http.Request) + + // (POST /unspecified-content-type) + UnspecifiedContentType(w http.ResponseWriter, r *http.Request) + + // (POST /urlencoded) + URLEncodedExample(w http.ResponseWriter, r *http.Request) + + // (POST /with-headers) + HeadersExample(w http.ResponseWriter, r *http.Request, params HeadersExampleParams) + + // (POST /with-union) + UnionExample(w http.ResponseWriter, r *http.Request) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// JSONExample operation middleware +func (siw *ServerInterfaceWrapper) JSONExample(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.JSONExample(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// MultipartExample operation middleware +func (siw *ServerInterfaceWrapper) MultipartExample(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.MultipartExample(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// MultipartRelatedExample operation middleware +func (siw *ServerInterfaceWrapper) MultipartRelatedExample(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.MultipartRelatedExample(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// MultipleRequestAndResponseTypes operation middleware +func (siw *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.MultipleRequestAndResponseTypes(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// RequiredJSONBody operation middleware +func (siw *ServerInterfaceWrapper) RequiredJSONBody(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.RequiredJSONBody(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// RequiredTextBody operation middleware +func (siw *ServerInterfaceWrapper) RequiredTextBody(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.RequiredTextBody(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ReservedGoKeywordParameters operation middleware +func (siw *ServerInterfaceWrapper) ReservedGoKeywordParameters(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "type" ------------- + var pType string + + err = runtime.BindStyledParameterWithOptions("simple", "type", mux.Vars(r)["type"], &pType, runtime.BindStyledParameterOptions{Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "type", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ReservedGoKeywordParameters(w, r, pType) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ReusableResponses operation middleware +func (siw *ServerInterfaceWrapper) ReusableResponses(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ReusableResponses(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// TextExample operation middleware +func (siw *ServerInterfaceWrapper) TextExample(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.TextExample(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// UnknownExample operation middleware +func (siw *ServerInterfaceWrapper) UnknownExample(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UnknownExample(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// UnspecifiedContentType operation middleware +func (siw *ServerInterfaceWrapper) UnspecifiedContentType(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UnspecifiedContentType(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// URLEncodedExample operation middleware +func (siw *ServerInterfaceWrapper) URLEncodedExample(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.URLEncodedExample(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// HeadersExample operation middleware +func (siw *ServerInterfaceWrapper) HeadersExample(w http.ResponseWriter, r *http.Request) { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params HeadersExampleParams + + headers := r.Header + + // ------------- Required header parameter "header1" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("header1")]; found { + var Header1 string + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "header1", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "header1", valueList[0], &Header1, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "header1", Err: err}) + return + } + + params.Header1 = Header1 + + } else { + err = fmt.Errorf("Header parameter header1 is required, but not found") + siw.ErrorHandlerFunc(w, r, &RequiredHeaderError{ParamName: "header1", Err: err}) + return + } + + // ------------- Optional header parameter "header2" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("header2")]; found { + var Header2 int + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "header2", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "header2", valueList[0], &Header2, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false, Type: "integer", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "header2", Err: err}) + return + } + + params.Header2 = &Header2 + + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.HeadersExample(w, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// UnionExample operation middleware +func (siw *ServerInterfaceWrapper) UnionExample(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UnionExample(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{}) +} + +type GorillaServerOptions struct { + BaseURL string + BaseRouter *mux.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r *mux.Router) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r *mux.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options GorillaServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = mux.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.HandleFunc(options.BaseURL+"/json", wrapper.JSONExample).Methods("POST") + + r.HandleFunc(options.BaseURL+"/multipart", wrapper.MultipartExample).Methods("POST") + + r.HandleFunc(options.BaseURL+"/multipart-related", wrapper.MultipartRelatedExample).Methods("POST") + + r.HandleFunc(options.BaseURL+"/multiple", wrapper.MultipleRequestAndResponseTypes).Methods("POST") + + r.HandleFunc(options.BaseURL+"/required-json-body", wrapper.RequiredJSONBody).Methods("POST") + + r.HandleFunc(options.BaseURL+"/required-text-body", wrapper.RequiredTextBody).Methods("POST") + + r.HandleFunc(options.BaseURL+"/reserved-go-keyword-parameters/{type}", wrapper.ReservedGoKeywordParameters).Methods("GET") + + r.HandleFunc(options.BaseURL+"/reusable-responses", wrapper.ReusableResponses).Methods("POST") + + r.HandleFunc(options.BaseURL+"/text", wrapper.TextExample).Methods("POST") + + r.HandleFunc(options.BaseURL+"/unknown", wrapper.UnknownExample).Methods("POST") + + r.HandleFunc(options.BaseURL+"/unspecified-content-type", wrapper.UnspecifiedContentType).Methods("POST") + + r.HandleFunc(options.BaseURL+"/urlencoded", wrapper.URLEncodedExample).Methods("POST") + + r.HandleFunc(options.BaseURL+"/with-headers", wrapper.HeadersExample).Methods("POST") + + r.HandleFunc(options.BaseURL+"/with-union", wrapper.UnionExample).Methods("POST") + + return r +} + +type BadrequestResponse struct { +} + +type ReusableresponseResponseHeaders struct { + Header1 string + Header2 int +} +type ReusableresponseJSONResponse struct { + Body Example + + Headers ReusableresponseResponseHeaders +} + +type JSONExampleRequestObject struct { + Body *JSONExampleJSONRequestBody +} + +type JSONExampleResponseObject interface { + VisitJSONExampleResponse(w http.ResponseWriter) error +} + +type JSONExample200JSONResponse Example + +func (response JSONExample200JSONResponse) VisitJSONExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type JSONExample400Response = BadrequestResponse + +func (response JSONExample400Response) VisitJSONExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type JSONExampledefaultResponse struct { + StatusCode int +} + +func (response JSONExampledefaultResponse) VisitJSONExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type MultipartExampleRequestObject struct { + Body *multipart.Reader +} + +type MultipartExampleResponseObject interface { + VisitMultipartExampleResponse(w http.ResponseWriter) error +} + +type MultipartExample200MultipartResponse func(writer *multipart.Writer) error + +func (response MultipartExample200MultipartResponse) VisitMultipartExampleResponse(w http.ResponseWriter) error { + writer := multipart.NewWriter(w) + w.Header().Set("Content-Type", writer.FormDataContentType()) + w.WriteHeader(200) + + defer writer.Close() + return response(writer) +} + +type MultipartExample400Response = BadrequestResponse + +func (response MultipartExample400Response) VisitMultipartExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type MultipartExampledefaultResponse struct { + StatusCode int +} + +func (response MultipartExampledefaultResponse) VisitMultipartExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type MultipartRelatedExampleRequestObject struct { + Body *multipart.Reader +} + +type MultipartRelatedExampleResponseObject interface { + VisitMultipartRelatedExampleResponse(w http.ResponseWriter) error +} + +type MultipartRelatedExample200MultipartResponse func(writer *multipart.Writer) error + +func (response MultipartRelatedExample200MultipartResponse) VisitMultipartRelatedExampleResponse(w http.ResponseWriter) error { + writer := multipart.NewWriter(w) + w.Header().Set("Content-Type", mime.FormatMediaType("multipart/related", map[string]string{"boundary": writer.Boundary()})) + w.WriteHeader(200) + + defer writer.Close() + return response(writer) +} + +type MultipartRelatedExample400Response = BadrequestResponse + +func (response MultipartRelatedExample400Response) VisitMultipartRelatedExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type MultipartRelatedExampledefaultResponse struct { + StatusCode int +} + +func (response MultipartRelatedExampledefaultResponse) VisitMultipartRelatedExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type MultipleRequestAndResponseTypesRequestObject struct { + JSONBody *MultipleRequestAndResponseTypesJSONRequestBody + FormdataBody *MultipleRequestAndResponseTypesFormdataRequestBody + Body io.Reader + MultipartBody *multipart.Reader + TextBody *MultipleRequestAndResponseTypesTextRequestBody +} + +type MultipleRequestAndResponseTypesResponseObject interface { + VisitMultipleRequestAndResponseTypesResponse(w http.ResponseWriter) error +} + +type MultipleRequestAndResponseTypes200JSONResponse Example + +func (response MultipleRequestAndResponseTypes200JSONResponse) VisitMultipleRequestAndResponseTypesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type MultipleRequestAndResponseTypes200FormdataResponse Example + +func (response MultipleRequestAndResponseTypes200FormdataResponse) VisitMultipleRequestAndResponseTypesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/x-www-form-urlencoded") + w.WriteHeader(200) + + if form, err := runtime.MarshalForm(response, nil); err != nil { + return err + } else { + _, err := w.Write([]byte(form.Encode())) + return err + } +} + +type MultipleRequestAndResponseTypes200ImagepngResponse struct { + Body io.Reader + ContentLength int64 +} + +func (response MultipleRequestAndResponseTypes200ImagepngResponse) VisitMultipleRequestAndResponseTypesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "image/png") + if response.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + w.WriteHeader(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(w, response.Body) + return err +} + +type MultipleRequestAndResponseTypes200MultipartResponse func(writer *multipart.Writer) error + +func (response MultipleRequestAndResponseTypes200MultipartResponse) VisitMultipleRequestAndResponseTypesResponse(w http.ResponseWriter) error { + writer := multipart.NewWriter(w) + w.Header().Set("Content-Type", writer.FormDataContentType()) + w.WriteHeader(200) + + defer writer.Close() + return response(writer) +} + +type MultipleRequestAndResponseTypes200TextResponse string + +func (response MultipleRequestAndResponseTypes200TextResponse) VisitMultipleRequestAndResponseTypesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(200) + + _, err := w.Write([]byte(response)) + return err +} + +type MultipleRequestAndResponseTypes400Response = BadrequestResponse + +func (response MultipleRequestAndResponseTypes400Response) VisitMultipleRequestAndResponseTypesResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type RequiredJSONBodyRequestObject struct { + Body *RequiredJSONBodyJSONRequestBody +} + +type RequiredJSONBodyResponseObject interface { + VisitRequiredJSONBodyResponse(w http.ResponseWriter) error +} + +type RequiredJSONBody200JSONResponse Example + +func (response RequiredJSONBody200JSONResponse) VisitRequiredJSONBodyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type RequiredJSONBody400Response = BadrequestResponse + +func (response RequiredJSONBody400Response) VisitRequiredJSONBodyResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type RequiredJSONBodydefaultResponse struct { + StatusCode int +} + +func (response RequiredJSONBodydefaultResponse) VisitRequiredJSONBodyResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type RequiredTextBodyRequestObject struct { + Body *RequiredTextBodyTextRequestBody +} + +type RequiredTextBodyResponseObject interface { + VisitRequiredTextBodyResponse(w http.ResponseWriter) error +} + +type RequiredTextBody200TextResponse string + +func (response RequiredTextBody200TextResponse) VisitRequiredTextBodyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(200) + + _, err := w.Write([]byte(response)) + return err +} + +type RequiredTextBody400Response = BadrequestResponse + +func (response RequiredTextBody400Response) VisitRequiredTextBodyResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type RequiredTextBodydefaultResponse struct { + StatusCode int +} + +func (response RequiredTextBodydefaultResponse) VisitRequiredTextBodyResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type ReservedGoKeywordParametersRequestObject struct { + Type string `json:"type"` +} + +type ReservedGoKeywordParametersResponseObject interface { + VisitReservedGoKeywordParametersResponse(w http.ResponseWriter) error +} + +type ReservedGoKeywordParameters200TextResponse string + +func (response ReservedGoKeywordParameters200TextResponse) VisitReservedGoKeywordParametersResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(200) + + _, err := w.Write([]byte(response)) + return err +} + +type ReusableResponsesRequestObject struct { + Body *ReusableResponsesJSONRequestBody +} + +type ReusableResponsesResponseObject interface { + VisitReusableResponsesResponse(w http.ResponseWriter) error +} + +type ReusableResponses200JSONResponse struct{ ReusableresponseJSONResponse } + +func (response ReusableResponses200JSONResponse) VisitReusableResponsesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) + w.Header().Set("header2", fmt.Sprint(response.Headers.Header2)) + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response.Body) +} + +type ReusableResponses400Response = BadrequestResponse + +func (response ReusableResponses400Response) VisitReusableResponsesResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type ReusableResponsesdefaultResponse struct { + StatusCode int +} + +func (response ReusableResponsesdefaultResponse) VisitReusableResponsesResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type TextExampleRequestObject struct { + Body *TextExampleTextRequestBody +} + +type TextExampleResponseObject interface { + VisitTextExampleResponse(w http.ResponseWriter) error +} + +type TextExample200TextResponse string + +func (response TextExample200TextResponse) VisitTextExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(200) + + _, err := w.Write([]byte(response)) + return err +} + +type TextExample400Response = BadrequestResponse + +func (response TextExample400Response) VisitTextExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type TextExampledefaultResponse struct { + StatusCode int +} + +func (response TextExampledefaultResponse) VisitTextExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type UnknownExampleRequestObject struct { + Body io.Reader +} + +type UnknownExampleResponseObject interface { + VisitUnknownExampleResponse(w http.ResponseWriter) error +} + +type UnknownExample200Videomp4Response struct { + Body io.Reader + ContentLength int64 +} + +func (response UnknownExample200Videomp4Response) VisitUnknownExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "video/mp4") + if response.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + w.WriteHeader(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(w, response.Body) + return err +} + +type UnknownExample400Response = BadrequestResponse + +func (response UnknownExample400Response) VisitUnknownExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type UnknownExampledefaultResponse struct { + StatusCode int +} + +func (response UnknownExampledefaultResponse) VisitUnknownExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type UnspecifiedContentTypeRequestObject struct { + ContentType string + Body io.Reader +} + +type UnspecifiedContentTypeResponseObject interface { + VisitUnspecifiedContentTypeResponse(w http.ResponseWriter) error +} + +type UnspecifiedContentType200VideoResponse struct { + Body io.Reader + ContentType string + ContentLength int64 +} + +func (response UnspecifiedContentType200VideoResponse) VisitUnspecifiedContentTypeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", response.ContentType) + if response.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + w.WriteHeader(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(w, response.Body) + return err +} + +type UnspecifiedContentType400Response = BadrequestResponse + +func (response UnspecifiedContentType400Response) VisitUnspecifiedContentTypeResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type UnspecifiedContentType401Response struct { +} + +func (response UnspecifiedContentType401Response) VisitUnspecifiedContentTypeResponse(w http.ResponseWriter) error { + w.WriteHeader(401) + return nil +} + +type UnspecifiedContentType403Response struct { +} + +func (response UnspecifiedContentType403Response) VisitUnspecifiedContentTypeResponse(w http.ResponseWriter) error { + w.WriteHeader(403) + return nil +} + +type UnspecifiedContentTypedefaultResponse struct { + StatusCode int +} + +func (response UnspecifiedContentTypedefaultResponse) VisitUnspecifiedContentTypeResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type URLEncodedExampleRequestObject struct { + Body *URLEncodedExampleFormdataRequestBody +} + +type URLEncodedExampleResponseObject interface { + VisitURLEncodedExampleResponse(w http.ResponseWriter) error +} + +type URLEncodedExample200FormdataResponse Example + +func (response URLEncodedExample200FormdataResponse) VisitURLEncodedExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/x-www-form-urlencoded") + w.WriteHeader(200) + + if form, err := runtime.MarshalForm(response, nil); err != nil { + return err + } else { + _, err := w.Write([]byte(form.Encode())) + return err + } +} + +type URLEncodedExample400Response = BadrequestResponse + +func (response URLEncodedExample400Response) VisitURLEncodedExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type URLEncodedExampledefaultResponse struct { + StatusCode int +} + +func (response URLEncodedExampledefaultResponse) VisitURLEncodedExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type HeadersExampleRequestObject struct { + Params HeadersExampleParams + Body *HeadersExampleJSONRequestBody +} + +type HeadersExampleResponseObject interface { + VisitHeadersExampleResponse(w http.ResponseWriter) error +} + +type HeadersExample200ResponseHeaders struct { + Header1 string + Header2 int +} + +type HeadersExample200JSONResponse struct { + Body Example + Headers HeadersExample200ResponseHeaders +} + +func (response HeadersExample200JSONResponse) VisitHeadersExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) + w.Header().Set("header2", fmt.Sprint(response.Headers.Header2)) + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response.Body) +} + +type HeadersExample400Response = BadrequestResponse + +func (response HeadersExample400Response) VisitHeadersExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type HeadersExampledefaultResponse struct { + StatusCode int +} + +func (response HeadersExampledefaultResponse) VisitHeadersExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type UnionExampleRequestObject struct { + Body *UnionExampleJSONRequestBody +} + +type UnionExampleResponseObject interface { + VisitUnionExampleResponse(w http.ResponseWriter) error +} + +type UnionExample200ResponseHeaders struct { + Header1 string + Header2 int +} + +type UnionExample200ApplicationAlternativePlusJSONResponse struct { + Body Example + Headers UnionExample200ResponseHeaders +} + +func (response UnionExample200ApplicationAlternativePlusJSONResponse) VisitUnionExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/alternative+json") + w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) + w.Header().Set("header2", fmt.Sprint(response.Headers.Header2)) + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response.Body) +} + +type UnionExample200JSONResponse struct { + Body struct { + union json.RawMessage + } + Headers UnionExample200ResponseHeaders +} + +func (response UnionExample200JSONResponse) VisitUnionExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) + w.Header().Set("header2", fmt.Sprint(response.Headers.Header2)) + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response.Body.union) +} + +type UnionExample400Response = BadrequestResponse + +func (response UnionExample400Response) VisitUnionExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type UnionExampledefaultResponse struct { + StatusCode int +} + +func (response UnionExampledefaultResponse) VisitUnionExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (POST /json) + JSONExample(ctx context.Context, request JSONExampleRequestObject) (JSONExampleResponseObject, error) + + // (POST /multipart) + MultipartExample(ctx context.Context, request MultipartExampleRequestObject) (MultipartExampleResponseObject, error) + + // (POST /multipart-related) + MultipartRelatedExample(ctx context.Context, request MultipartRelatedExampleRequestObject) (MultipartRelatedExampleResponseObject, error) + + // (POST /multiple) + MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) + + // (POST /required-json-body) + RequiredJSONBody(ctx context.Context, request RequiredJSONBodyRequestObject) (RequiredJSONBodyResponseObject, error) + + // (POST /required-text-body) + RequiredTextBody(ctx context.Context, request RequiredTextBodyRequestObject) (RequiredTextBodyResponseObject, error) + + // (GET /reserved-go-keyword-parameters/{type}) + ReservedGoKeywordParameters(ctx context.Context, request ReservedGoKeywordParametersRequestObject) (ReservedGoKeywordParametersResponseObject, error) + + // (POST /reusable-responses) + ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) + + // (POST /text) + TextExample(ctx context.Context, request TextExampleRequestObject) (TextExampleResponseObject, error) + + // (POST /unknown) + UnknownExample(ctx context.Context, request UnknownExampleRequestObject) (UnknownExampleResponseObject, error) + + // (POST /unspecified-content-type) + UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) (UnspecifiedContentTypeResponseObject, error) + + // (POST /urlencoded) + URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) (URLEncodedExampleResponseObject, error) + + // (POST /with-headers) + HeadersExample(ctx context.Context, request HeadersExampleRequestObject) (HeadersExampleResponseObject, error) + + // (POST /with-union) + UnionExample(ctx context.Context, request UnionExampleRequestObject) (UnionExampleResponseObject, error) +} + +type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc +type StrictMiddlewareFunc = strictnethttp.StrictHTTPMiddlewareFunc + +type StrictHTTPServerOptions struct { + RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictHTTPServerOptions{ + RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + }, + ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusInternalServerError) + }, + }} +} + +func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictHTTPServerOptions) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: options} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc + options StrictHTTPServerOptions +} + +// JSONExample operation middleware +func (sh *strictHandler) JSONExample(w http.ResponseWriter, r *http.Request) { + var request JSONExampleRequestObject + + var body JSONExampleJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + if !errors.Is(err, io.EOF) { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + } else { + request.Body = &body + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.JSONExample(ctx, request.(JSONExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "JSONExample") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(JSONExampleResponseObject); ok { + if err := validResponse.VisitJSONExampleResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// MultipartExample operation middleware +func (sh *strictHandler) MultipartExample(w http.ResponseWriter, r *http.Request) { + var request MultipartExampleRequestObject + + if reader, err := r.MultipartReader(); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode multipart body: %w", err)) + return + } else { + request.Body = reader + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.MultipartExample(ctx, request.(MultipartExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipartExample") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(MultipartExampleResponseObject); ok { + if err := validResponse.VisitMultipartExampleResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// MultipartRelatedExample operation middleware +func (sh *strictHandler) MultipartRelatedExample(w http.ResponseWriter, r *http.Request) { + var request MultipartRelatedExampleRequestObject + + if _, params, err := mime.ParseMediaType(r.Header.Get("Content-Type")); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, err) + return + } else if boundary := params["boundary"]; boundary == "" { + sh.options.RequestErrorHandlerFunc(w, r, http.ErrMissingBoundary) + return + } else { + request.Body = multipart.NewReader(r.Body, boundary) + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.MultipartRelatedExample(ctx, request.(MultipartRelatedExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipartRelatedExample") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(MultipartRelatedExampleResponseObject); ok { + if err := validResponse.VisitMultipartRelatedExampleResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// MultipleRequestAndResponseTypes operation middleware +func (sh *strictHandler) MultipleRequestAndResponseTypes(w http.ResponseWriter, r *http.Request) { + var request MultipleRequestAndResponseTypesRequestObject + + if strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") { + + var body MultipleRequestAndResponseTypesJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + if !errors.Is(err, io.EOF) { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + } else { + request.JSONBody = &body + } + } + if strings.HasPrefix(r.Header.Get("Content-Type"), "application/x-www-form-urlencoded") { + if err := r.ParseForm(); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode formdata: %w", err)) + return + } + var body MultipleRequestAndResponseTypesFormdataRequestBody + if err := runtime.BindForm(&body, r.Form, nil, nil); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't bind formdata: %w", err)) + return + } + request.FormdataBody = &body + } + if strings.HasPrefix(r.Header.Get("Content-Type"), "image/png") { + request.Body = r.Body + } + if strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/form-data") { + if reader, err := r.MultipartReader(); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode multipart body: %w", err)) + return + } else { + request.MultipartBody = reader + } + } + if strings.HasPrefix(r.Header.Get("Content-Type"), "text/plain") { + data, err := io.ReadAll(r.Body) + if err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't read body: %w", err)) + return + } + if len(data) > 0 { + body := MultipleRequestAndResponseTypesTextRequestBody(data) + request.TextBody = &body + } + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.MultipleRequestAndResponseTypes(ctx, request.(MultipleRequestAndResponseTypesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipleRequestAndResponseTypes") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(MultipleRequestAndResponseTypesResponseObject); ok { + if err := validResponse.VisitMultipleRequestAndResponseTypesResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// RequiredJSONBody operation middleware +func (sh *strictHandler) RequiredJSONBody(w http.ResponseWriter, r *http.Request) { + var request RequiredJSONBodyRequestObject + + var body RequiredJSONBodyJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.RequiredJSONBody(ctx, request.(RequiredJSONBodyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "RequiredJSONBody") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(RequiredJSONBodyResponseObject); ok { + if err := validResponse.VisitRequiredJSONBodyResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// RequiredTextBody operation middleware +func (sh *strictHandler) RequiredTextBody(w http.ResponseWriter, r *http.Request) { + var request RequiredTextBodyRequestObject + + data, err := io.ReadAll(r.Body) + if err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't read body: %w", err)) + return + } + body := RequiredTextBodyTextRequestBody(data) + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.RequiredTextBody(ctx, request.(RequiredTextBodyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "RequiredTextBody") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(RequiredTextBodyResponseObject); ok { + if err := validResponse.VisitRequiredTextBodyResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// ReservedGoKeywordParameters operation middleware +func (sh *strictHandler) ReservedGoKeywordParameters(w http.ResponseWriter, r *http.Request, pType string) { + var request ReservedGoKeywordParametersRequestObject + + request.Type = pType + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.ReservedGoKeywordParameters(ctx, request.(ReservedGoKeywordParametersRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReservedGoKeywordParameters") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(ReservedGoKeywordParametersResponseObject); ok { + if err := validResponse.VisitReservedGoKeywordParametersResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// ReusableResponses operation middleware +func (sh *strictHandler) ReusableResponses(w http.ResponseWriter, r *http.Request) { + var request ReusableResponsesRequestObject + + var body ReusableResponsesJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + if !errors.Is(err, io.EOF) { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + } else { + request.Body = &body + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.ReusableResponses(ctx, request.(ReusableResponsesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReusableResponses") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(ReusableResponsesResponseObject); ok { + if err := validResponse.VisitReusableResponsesResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// TextExample operation middleware +func (sh *strictHandler) TextExample(w http.ResponseWriter, r *http.Request) { + var request TextExampleRequestObject + + data, err := io.ReadAll(r.Body) + if err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't read body: %w", err)) + return + } + if len(data) > 0 { + body := TextExampleTextRequestBody(data) + request.Body = &body + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.TextExample(ctx, request.(TextExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "TextExample") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(TextExampleResponseObject); ok { + if err := validResponse.VisitTextExampleResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// UnknownExample operation middleware +func (sh *strictHandler) UnknownExample(w http.ResponseWriter, r *http.Request) { + var request UnknownExampleRequestObject + + request.Body = r.Body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.UnknownExample(ctx, request.(UnknownExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnknownExample") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(UnknownExampleResponseObject); ok { + if err := validResponse.VisitUnknownExampleResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// UnspecifiedContentType operation middleware +func (sh *strictHandler) UnspecifiedContentType(w http.ResponseWriter, r *http.Request) { + var request UnspecifiedContentTypeRequestObject + + request.ContentType = r.Header.Get("Content-Type") + + request.Body = r.Body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.UnspecifiedContentType(ctx, request.(UnspecifiedContentTypeRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnspecifiedContentType") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(UnspecifiedContentTypeResponseObject); ok { + if err := validResponse.VisitUnspecifiedContentTypeResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// URLEncodedExample operation middleware +func (sh *strictHandler) URLEncodedExample(w http.ResponseWriter, r *http.Request) { + var request URLEncodedExampleRequestObject + + if err := r.ParseForm(); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode formdata: %w", err)) + return + } + var body URLEncodedExampleFormdataRequestBody + if err := runtime.BindForm(&body, r.Form, nil, nil); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't bind formdata: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.URLEncodedExample(ctx, request.(URLEncodedExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "URLEncodedExample") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(URLEncodedExampleResponseObject); ok { + if err := validResponse.VisitURLEncodedExampleResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// HeadersExample operation middleware +func (sh *strictHandler) HeadersExample(w http.ResponseWriter, r *http.Request, params HeadersExampleParams) { + var request HeadersExampleRequestObject + + request.Params = params + + var body HeadersExampleJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + if !errors.Is(err, io.EOF) { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + } else { + request.Body = &body + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.HeadersExample(ctx, request.(HeadersExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "HeadersExample") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(HeadersExampleResponseObject); ok { + if err := validResponse.VisitHeadersExampleResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// UnionExample operation middleware +func (sh *strictHandler) UnionExample(w http.ResponseWriter, r *http.Request) { + var request UnionExampleRequestObject + + var body UnionExampleJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + if !errors.Is(err, io.EOF) { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + } else { + request.Body = &body + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.UnionExample(ctx, request.(UnionExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnionExample") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(UnionExampleResponseObject); ok { + if err := validResponse.VisitUnionExampleResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/+xZTXPbNhP+Kzt431NKmrbjE2+NJ5O2aeuObJ86PkDESkJCAiiwFK3R6L93QEBfFq1K", + "qT4ynt5Ecr/wPLuLBTRlha6MVqjIsXzKLDqjlcP2oc+Fxb9qdOSfBLrCSkNSK5azD1z04rdZwizWjvdL", + "nKt7+UIrQtWqcmNKWXCvmn1xXn/KXDHCivtf/7c4YDn7X7YMJQtfXYbPvDIlstlslryI4O4zS9gIuUDb", + "Rht+Xq3bpolBljNHVqoh80aC2HWnmFSEQ7TemxeNQXiBeRz5lBmrDVqSAaMxL2vs9hTf6P4XLCisQKqB", + "3sTyViviUjkQcjBAi4ogggfehgNXG6MtoYD+BLyHgsChHaNlCSNJPjB2v/oeYsCOJWyM1gVHVxeXF5ee", + "L21QcSNZzt63rxJmOI3aBS0IMrqL91/u734H6YDXpCtOsuBlOYGKWzfiJQqQirQPsS7IXbDWk22J/1lE", + "7Y8RSp81bQJ90GJyjIRp83Ilna8vL0+Ul7OE3QRnXTYWQWUrBdaaGfC67MD8UX1VulGA1mobV5ZVdUnS", + "cEurXK2j/dtcZBfIF/aygbZVKjjxI6F+KE/nBj61WHJCsQMBvSC5Hw8r5o/Kwr/xc1YOYj/u7FP3I904", + "GOkGSINAXkIjaQRzxRcNVirg4KQalgjzoJJOMkuM296PSvTiWh68jaP3s2TNynPaNE3aFlBtS1SFFt9G", + "YcJkxYeYGTVcV/e2ObGc9SfkU3ZzgztQISeM8JkyU3Kptu/eJ2rp/yF9sMIO5eofpUWRekbSfqyP7sKN", + "5QVeyg8ac90EnIZKOl+l4aMb6boUwMuGT1zoD5sTRy+q+8mjLcyjjx3BgffJcrI1vu0xZEGtz6zzUPuA", + "z/SP1O6R+PvSd+qa2p+i9kwg0qFOv+Kk0VakhlteIaF12dTHOfO2hthh8o+FJBRcQR9B8QoF8AGhhU8a", + "oknXwU/w+0l/DiJLU+2BY/GQ/zllHrz2EMIS5h2wPOD3ko5kC8BPx6VqjmY46qZrrl5L+Cgyh87iwPmB", + "pIvjDvyCp96KxHmOTNtzc+Pwf4qk9ky+Pnj7lrDLsH3AweN77wJ1ePk6ZlFrF9i+cY7ZAcWxFKizytzs", + "aflsoDqDhRxIFGlcRRpie60l3GpVWKT1A4jfDJUmWBiD/gRohBAQaPfHBqGqHYHhzoGktouUMtwVCdxo", + "Ho/LyG6Dp4dlO93G6rsjcfruXIzeXF7tr/L+yHmzdpB4pR57v34MMvvemB3sxLLneetwfs9Uzo2kUbpy", + "pdxdwj8FgeWeXqAc+4lICbBItVUoYCz5/Bp0ozajgSWtXbNQCGM5Dc2vt/cZiJKttq5Zsu0K/OkNX9Ae", + "84+DU+VpreS2i/pH/xniDP1yb5BafafX8LwktIqTHOMPh7m/2bSiFd4N2kp7wXKyo4ent5dVs4SFf45C", + "C6pt6fsEkcmzLPzjdOEaPhyivZA640Z6FP4OAAD//0nTejA+HAAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/strict-server/gorilla/server.go b/internal/test/strict-server/gorilla/server.go new file mode 100644 index 0000000000..2cb801e035 --- /dev/null +++ b/internal/test/strict-server/gorilla/server.go @@ -0,0 +1,128 @@ +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=server.cfg.yaml ../strict-schema.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=types.cfg.yaml ../strict-schema.yaml + +package api + +import ( + "context" + "encoding/json" + "io" + "mime/multipart" +) + +type StrictServer struct { +} + +func (s StrictServer) JSONExample(ctx context.Context, request JSONExampleRequestObject) (JSONExampleResponseObject, error) { + return JSONExample200JSONResponse(*request.Body), nil +} + +func (s StrictServer) MultipartExample(ctx context.Context, request MultipartExampleRequestObject) (MultipartExampleResponseObject, error) { + return MultipartExample200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.Body.NextPart() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + w, err := writer.CreatePart(part.Header) + if err != nil { + return err + } + _, err = io.Copy(w, part) + if err != nil { + return err + } + if err = part.Close(); err != nil { + return err + } + } + }), nil +} + +func (s StrictServer) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) { + switch { + case request.Body != nil: + return MultipleRequestAndResponseTypes200ImagepngResponse{Body: request.Body}, nil + case request.JSONBody != nil: + return MultipleRequestAndResponseTypes200JSONResponse(*request.JSONBody), nil + case request.FormdataBody != nil: + return MultipleRequestAndResponseTypes200FormdataResponse(*request.FormdataBody), nil + case request.TextBody != nil: + return MultipleRequestAndResponseTypes200TextResponse(*request.TextBody), nil + case request.MultipartBody != nil: + return MultipleRequestAndResponseTypes200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.MultipartBody.NextPart() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + w, err := writer.CreatePart(part.Header) + if err != nil { + return err + } + _, err = io.Copy(w, part) + if err != nil { + return err + } + if err = part.Close(); err != nil { + return err + } + } + }), nil + default: + return MultipleRequestAndResponseTypes400Response{}, nil + } +} + +func (s StrictServer) TextExample(ctx context.Context, request TextExampleRequestObject) (TextExampleResponseObject, error) { + return TextExample200TextResponse(*request.Body), nil +} + +func (s StrictServer) UnknownExample(ctx context.Context, request UnknownExampleRequestObject) (UnknownExampleResponseObject, error) { + return UnknownExample200Videomp4Response{Body: request.Body}, nil +} + +func (s StrictServer) UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) (UnspecifiedContentTypeResponseObject, error) { + return UnspecifiedContentType200VideoResponse{Body: request.Body, ContentType: request.ContentType}, nil +} + +func (s StrictServer) URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) (URLEncodedExampleResponseObject, error) { + return URLEncodedExample200FormdataResponse(*request.Body), nil +} + +func (s StrictServer) HeadersExample(ctx context.Context, request HeadersExampleRequestObject) (HeadersExampleResponseObject, error) { + return HeadersExample200JSONResponse{Body: *request.Body, Headers: HeadersExample200ResponseHeaders{Header1: request.Params.Header1, Header2: *request.Params.Header2}}, nil +} + +func (s StrictServer) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) { + return ReusableResponses200JSONResponse{ReusableresponseJSONResponse: ReusableresponseJSONResponse{Body: *request.Body}}, nil +} + +func (s StrictServer) RequiredJSONBody(ctx context.Context, request RequiredJSONBodyRequestObject) (RequiredJSONBodyResponseObject, error) { + return RequiredJSONBody200JSONResponse(*request.Body), nil +} + +func (s StrictServer) RequiredTextBody(ctx context.Context, request RequiredTextBodyRequestObject) (RequiredTextBodyResponseObject, error) { + return RequiredTextBody200TextResponse(*request.Body), nil +} + +func (s StrictServer) ReservedGoKeywordParameters(ctx context.Context, request ReservedGoKeywordParametersRequestObject) (ReservedGoKeywordParametersResponseObject, error) { + return ReservedGoKeywordParameters200TextResponse(""), nil +} + +func (s StrictServer) UnionExample(ctx context.Context, request UnionExampleRequestObject) (UnionExampleResponseObject, error) { + union, err := json.Marshal(*request.Body) + if err != nil { + return nil, err + } + + return UnionExample200JSONResponse{ + Body: struct{ union json.RawMessage }{ + union: union, + }, + }, nil +} diff --git a/internal/test/strict-server/gorilla/types.cfg.yaml b/internal/test/strict-server/gorilla/types.cfg.yaml new file mode 100644 index 0000000000..2ed9740ea7 --- /dev/null +++ b/internal/test/strict-server/gorilla/types.cfg.yaml @@ -0,0 +1,5 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +generate: + models: true +output: types.gen.go diff --git a/internal/test/strict-server/gorilla/types.gen.go b/internal/test/strict-server/gorilla/types.gen.go new file mode 100644 index 0000000000..fac014c4bb --- /dev/null +++ b/internal/test/strict-server/gorilla/types.gen.go @@ -0,0 +1,69 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +// Example defines model for example. +type Example struct { + Value *string `json:"value,omitempty"` +} + +// Reusableresponse defines model for reusableresponse. +type Reusableresponse = Example + +// MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesTextBody = string + +// RequiredTextBodyTextBody defines parameters for RequiredTextBody. +type RequiredTextBodyTextBody = string + +// TextExampleTextBody defines parameters for TextExample. +type TextExampleTextBody = string + +// HeadersExampleParams defines parameters for HeadersExample. +type HeadersExampleParams struct { + Header1 string `json:"header1"` + Header2 *int `json:"header2,omitempty"` +} + +// JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. +type JSONExampleJSONRequestBody = Example + +// MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. +type MultipartExampleMultipartRequestBody = Example + +// MultipartRelatedExampleMultipartRequestBody defines body for MultipartRelatedExample for multipart/related ContentType. +type MultipartRelatedExampleMultipartRequestBody = Example + +// MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. +type MultipleRequestAndResponseTypesJSONRequestBody = Example + +// MultipleRequestAndResponseTypesFormdataRequestBody defines body for MultipleRequestAndResponseTypes for application/x-www-form-urlencoded ContentType. +type MultipleRequestAndResponseTypesFormdataRequestBody = Example + +// MultipleRequestAndResponseTypesMultipartRequestBody defines body for MultipleRequestAndResponseTypes for multipart/form-data ContentType. +type MultipleRequestAndResponseTypesMultipartRequestBody = Example + +// MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. +type MultipleRequestAndResponseTypesTextRequestBody = MultipleRequestAndResponseTypesTextBody + +// RequiredJSONBodyJSONRequestBody defines body for RequiredJSONBody for application/json ContentType. +type RequiredJSONBodyJSONRequestBody = Example + +// RequiredTextBodyTextRequestBody defines body for RequiredTextBody for text/plain ContentType. +type RequiredTextBodyTextRequestBody = RequiredTextBodyTextBody + +// ReusableResponsesJSONRequestBody defines body for ReusableResponses for application/json ContentType. +type ReusableResponsesJSONRequestBody = Example + +// TextExampleTextRequestBody defines body for TextExample for text/plain ContentType. +type TextExampleTextRequestBody = TextExampleTextBody + +// URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. +type URLEncodedExampleFormdataRequestBody = Example + +// HeadersExampleJSONRequestBody defines body for HeadersExample for application/json ContentType. +type HeadersExampleJSONRequestBody = Example + +// UnionExampleJSONRequestBody defines body for UnionExample for application/json ContentType. +type UnionExampleJSONRequestBody = Example diff --git a/internal/test/strict-server/iris/server.cfg.yaml b/internal/test/strict-server/iris/server.cfg.yaml new file mode 100644 index 0000000000..481f842dc6 --- /dev/null +++ b/internal/test/strict-server/iris/server.cfg.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +generate: + iris-server: true + strict-server: true + embedded-spec: true +output: server.gen.go diff --git a/internal/test/strict-server/iris/server.gen.go b/internal/test/strict-server/iris/server.gen.go new file mode 100644 index 0000000000..033e1dd769 --- /dev/null +++ b/internal/test/strict-server/iris/server.gen.go @@ -0,0 +1,1553 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "mime" + "mime/multipart" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/kataras/iris/v12" + "github.com/oapi-codegen/runtime" + strictiris "github.com/oapi-codegen/runtime/strictmiddleware/iris" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (POST /json) + JSONExample(ctx iris.Context) + + // (POST /multipart) + MultipartExample(ctx iris.Context) + + // (POST /multipart-related) + MultipartRelatedExample(ctx iris.Context) + + // (POST /multiple) + MultipleRequestAndResponseTypes(ctx iris.Context) + + // (POST /required-json-body) + RequiredJSONBody(ctx iris.Context) + + // (POST /required-text-body) + RequiredTextBody(ctx iris.Context) + + // (GET /reserved-go-keyword-parameters/{type}) + ReservedGoKeywordParameters(ctx iris.Context, pType string) + + // (POST /reusable-responses) + ReusableResponses(ctx iris.Context) + + // (POST /text) + TextExample(ctx iris.Context) + + // (POST /unknown) + UnknownExample(ctx iris.Context) + + // (POST /unspecified-content-type) + UnspecifiedContentType(ctx iris.Context) + + // (POST /urlencoded) + URLEncodedExample(ctx iris.Context) + + // (POST /with-headers) + HeadersExample(ctx iris.Context, params HeadersExampleParams) + + // (POST /with-union) + UnionExample(ctx iris.Context) +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +type MiddlewareFunc iris.Handler + +// JSONExample converts iris context to params. +func (w *ServerInterfaceWrapper) JSONExample(ctx iris.Context) { + + // Invoke the callback with all the unmarshaled arguments + w.Handler.JSONExample(ctx) +} + +// MultipartExample converts iris context to params. +func (w *ServerInterfaceWrapper) MultipartExample(ctx iris.Context) { + + // Invoke the callback with all the unmarshaled arguments + w.Handler.MultipartExample(ctx) +} + +// MultipartRelatedExample converts iris context to params. +func (w *ServerInterfaceWrapper) MultipartRelatedExample(ctx iris.Context) { + + // Invoke the callback with all the unmarshaled arguments + w.Handler.MultipartRelatedExample(ctx) +} + +// MultipleRequestAndResponseTypes converts iris context to params. +func (w *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(ctx iris.Context) { + + // Invoke the callback with all the unmarshaled arguments + w.Handler.MultipleRequestAndResponseTypes(ctx) +} + +// RequiredJSONBody converts iris context to params. +func (w *ServerInterfaceWrapper) RequiredJSONBody(ctx iris.Context) { + + // Invoke the callback with all the unmarshaled arguments + w.Handler.RequiredJSONBody(ctx) +} + +// RequiredTextBody converts iris context to params. +func (w *ServerInterfaceWrapper) RequiredTextBody(ctx iris.Context) { + + // Invoke the callback with all the unmarshaled arguments + w.Handler.RequiredTextBody(ctx) +} + +// ReservedGoKeywordParameters converts iris context to params. +func (w *ServerInterfaceWrapper) ReservedGoKeywordParameters(ctx iris.Context) { + + var err error + + // ------------- Path parameter "type" ------------- + var pType string + + err = runtime.BindStyledParameterWithOptions("simple", "type", ctx.Params().Get("type"), &pType, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + ctx.StatusCode(http.StatusBadRequest) + ctx.Writef("Invalid format for parameter type: %s", err) + return + } + + // Invoke the callback with all the unmarshaled arguments + w.Handler.ReservedGoKeywordParameters(ctx, pType) +} + +// ReusableResponses converts iris context to params. +func (w *ServerInterfaceWrapper) ReusableResponses(ctx iris.Context) { + + // Invoke the callback with all the unmarshaled arguments + w.Handler.ReusableResponses(ctx) +} + +// TextExample converts iris context to params. +func (w *ServerInterfaceWrapper) TextExample(ctx iris.Context) { + + // Invoke the callback with all the unmarshaled arguments + w.Handler.TextExample(ctx) +} + +// UnknownExample converts iris context to params. +func (w *ServerInterfaceWrapper) UnknownExample(ctx iris.Context) { + + // Invoke the callback with all the unmarshaled arguments + w.Handler.UnknownExample(ctx) +} + +// UnspecifiedContentType converts iris context to params. +func (w *ServerInterfaceWrapper) UnspecifiedContentType(ctx iris.Context) { + + // Invoke the callback with all the unmarshaled arguments + w.Handler.UnspecifiedContentType(ctx) +} + +// URLEncodedExample converts iris context to params. +func (w *ServerInterfaceWrapper) URLEncodedExample(ctx iris.Context) { + + // Invoke the callback with all the unmarshaled arguments + w.Handler.URLEncodedExample(ctx) +} + +// HeadersExample converts iris context to params. +func (w *ServerInterfaceWrapper) HeadersExample(ctx iris.Context) { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params HeadersExampleParams + + headers := ctx.Request().Header + // ------------- Required header parameter "header1" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("header1")]; found { + var Header1 string + n := len(valueList) + if n != 1 { + ctx.StatusCode(http.StatusBadRequest) + ctx.Writef("Expected one value for header1, got %d", n) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "header1", valueList[0], &Header1, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + ctx.StatusCode(http.StatusBadRequest) + ctx.Writef("Invalid format for parameter header1: %s", err) + return + } + + params.Header1 = Header1 + } else { + ctx.StatusCode(http.StatusBadRequest) + ctx.WriteString("Header header1 is required, but not found") + return + } + // ------------- Optional header parameter "header2" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("header2")]; found { + var Header2 int + n := len(valueList) + if n != 1 { + ctx.StatusCode(http.StatusBadRequest) + ctx.Writef("Expected one value for header2, got %d", n) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "header2", valueList[0], &Header2, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false, Type: "integer", Format: ""}) + if err != nil { + ctx.StatusCode(http.StatusBadRequest) + ctx.Writef("Invalid format for parameter header2: %s", err) + return + } + + params.Header2 = &Header2 + } + + // Invoke the callback with all the unmarshaled arguments + w.Handler.HeadersExample(ctx, params) +} + +// UnionExample converts iris context to params. +func (w *ServerInterfaceWrapper) UnionExample(ctx iris.Context) { + + // Invoke the callback with all the unmarshaled arguments + w.Handler.UnionExample(ctx) +} + +// IrisServerOption is the option for iris server +type IrisServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router *iris.Application, si ServerInterface) { + RegisterHandlersWithOptions(router, si, IrisServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router *iris.Application, si ServerInterface, options IrisServerOptions) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.Post(options.BaseURL+"/json", wrapper.JSONExample) + router.Post(options.BaseURL+"/multipart", wrapper.MultipartExample) + router.Post(options.BaseURL+"/multipart-related", wrapper.MultipartRelatedExample) + router.Post(options.BaseURL+"/multiple", wrapper.MultipleRequestAndResponseTypes) + router.Post(options.BaseURL+"/required-json-body", wrapper.RequiredJSONBody) + router.Post(options.BaseURL+"/required-text-body", wrapper.RequiredTextBody) + router.Get(options.BaseURL+"/reserved-go-keyword-parameters/:type", wrapper.ReservedGoKeywordParameters) + router.Post(options.BaseURL+"/reusable-responses", wrapper.ReusableResponses) + router.Post(options.BaseURL+"/text", wrapper.TextExample) + router.Post(options.BaseURL+"/unknown", wrapper.UnknownExample) + router.Post(options.BaseURL+"/unspecified-content-type", wrapper.UnspecifiedContentType) + router.Post(options.BaseURL+"/urlencoded", wrapper.URLEncodedExample) + router.Post(options.BaseURL+"/with-headers", wrapper.HeadersExample) + router.Post(options.BaseURL+"/with-union", wrapper.UnionExample) + + router.Build() +} + +type BadrequestResponse struct { +} + +type ReusableresponseResponseHeaders struct { + Header1 string + Header2 int +} +type ReusableresponseJSONResponse struct { + Body Example + + Headers ReusableresponseResponseHeaders +} + +type JSONExampleRequestObject struct { + Body *JSONExampleJSONRequestBody +} + +type JSONExampleResponseObject interface { + VisitJSONExampleResponse(ctx iris.Context) error +} + +type JSONExample200JSONResponse Example + +func (response JSONExample200JSONResponse) VisitJSONExampleResponse(ctx iris.Context) error { + ctx.ResponseWriter().Header().Set("Content-Type", "application/json") + ctx.StatusCode(200) + + return ctx.JSON(&response) +} + +type JSONExample400Response = BadrequestResponse + +func (response JSONExample400Response) VisitJSONExampleResponse(ctx iris.Context) error { + ctx.StatusCode(400) + return nil +} + +type JSONExampledefaultResponse struct { + StatusCode int +} + +func (response JSONExampledefaultResponse) VisitJSONExampleResponse(ctx iris.Context) error { + ctx.StatusCode(response.StatusCode) + return nil +} + +type MultipartExampleRequestObject struct { + Body *multipart.Reader +} + +type MultipartExampleResponseObject interface { + VisitMultipartExampleResponse(ctx iris.Context) error +} + +type MultipartExample200MultipartResponse func(writer *multipart.Writer) error + +func (response MultipartExample200MultipartResponse) VisitMultipartExampleResponse(ctx iris.Context) error { + writer := multipart.NewWriter(ctx.ResponseWriter()) + ctx.ResponseWriter().Header().Set("Content-Type", writer.FormDataContentType()) + ctx.StatusCode(200) + + defer writer.Close() + return response(writer) +} + +type MultipartExample400Response = BadrequestResponse + +func (response MultipartExample400Response) VisitMultipartExampleResponse(ctx iris.Context) error { + ctx.StatusCode(400) + return nil +} + +type MultipartExampledefaultResponse struct { + StatusCode int +} + +func (response MultipartExampledefaultResponse) VisitMultipartExampleResponse(ctx iris.Context) error { + ctx.StatusCode(response.StatusCode) + return nil +} + +type MultipartRelatedExampleRequestObject struct { + Body *multipart.Reader +} + +type MultipartRelatedExampleResponseObject interface { + VisitMultipartRelatedExampleResponse(ctx iris.Context) error +} + +type MultipartRelatedExample200MultipartResponse func(writer *multipart.Writer) error + +func (response MultipartRelatedExample200MultipartResponse) VisitMultipartRelatedExampleResponse(ctx iris.Context) error { + writer := multipart.NewWriter(ctx.ResponseWriter()) + ctx.ResponseWriter().Header().Set("Content-Type", mime.FormatMediaType("multipart/related", map[string]string{"boundary": writer.Boundary()})) + ctx.StatusCode(200) + + defer writer.Close() + return response(writer) +} + +type MultipartRelatedExample400Response = BadrequestResponse + +func (response MultipartRelatedExample400Response) VisitMultipartRelatedExampleResponse(ctx iris.Context) error { + ctx.StatusCode(400) + return nil +} + +type MultipartRelatedExampledefaultResponse struct { + StatusCode int +} + +func (response MultipartRelatedExampledefaultResponse) VisitMultipartRelatedExampleResponse(ctx iris.Context) error { + ctx.StatusCode(response.StatusCode) + return nil +} + +type MultipleRequestAndResponseTypesRequestObject struct { + JSONBody *MultipleRequestAndResponseTypesJSONRequestBody + FormdataBody *MultipleRequestAndResponseTypesFormdataRequestBody + Body io.Reader + MultipartBody *multipart.Reader + TextBody *MultipleRequestAndResponseTypesTextRequestBody +} + +type MultipleRequestAndResponseTypesResponseObject interface { + VisitMultipleRequestAndResponseTypesResponse(ctx iris.Context) error +} + +type MultipleRequestAndResponseTypes200JSONResponse Example + +func (response MultipleRequestAndResponseTypes200JSONResponse) VisitMultipleRequestAndResponseTypesResponse(ctx iris.Context) error { + ctx.ResponseWriter().Header().Set("Content-Type", "application/json") + ctx.StatusCode(200) + + return ctx.JSON(&response) +} + +type MultipleRequestAndResponseTypes200FormdataResponse Example + +func (response MultipleRequestAndResponseTypes200FormdataResponse) VisitMultipleRequestAndResponseTypesResponse(ctx iris.Context) error { + ctx.ResponseWriter().Header().Set("Content-Type", "application/x-www-form-urlencoded") + ctx.StatusCode(200) + + if form, err := runtime.MarshalForm(response, nil); err != nil { + return err + } else { + _, err := ctx.WriteString(form.Encode()) + return err + } +} + +type MultipleRequestAndResponseTypes200ImagepngResponse struct { + Body io.Reader + ContentLength int64 +} + +func (response MultipleRequestAndResponseTypes200ImagepngResponse) VisitMultipleRequestAndResponseTypesResponse(ctx iris.Context) error { + ctx.ResponseWriter().Header().Set("Content-Type", "image/png") + if response.ContentLength != 0 { + ctx.ResponseWriter().Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + ctx.StatusCode(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(ctx.ResponseWriter(), response.Body) + return err +} + +type MultipleRequestAndResponseTypes200MultipartResponse func(writer *multipart.Writer) error + +func (response MultipleRequestAndResponseTypes200MultipartResponse) VisitMultipleRequestAndResponseTypesResponse(ctx iris.Context) error { + writer := multipart.NewWriter(ctx.ResponseWriter()) + ctx.ResponseWriter().Header().Set("Content-Type", writer.FormDataContentType()) + ctx.StatusCode(200) + + defer writer.Close() + return response(writer) +} + +type MultipleRequestAndResponseTypes200TextResponse string + +func (response MultipleRequestAndResponseTypes200TextResponse) VisitMultipleRequestAndResponseTypesResponse(ctx iris.Context) error { + ctx.ResponseWriter().Header().Set("Content-Type", "text/plain") + ctx.StatusCode(200) + + _, err := ctx.WriteString(string(response)) + return err +} + +type MultipleRequestAndResponseTypes400Response = BadrequestResponse + +func (response MultipleRequestAndResponseTypes400Response) VisitMultipleRequestAndResponseTypesResponse(ctx iris.Context) error { + ctx.StatusCode(400) + return nil +} + +type RequiredJSONBodyRequestObject struct { + Body *RequiredJSONBodyJSONRequestBody +} + +type RequiredJSONBodyResponseObject interface { + VisitRequiredJSONBodyResponse(ctx iris.Context) error +} + +type RequiredJSONBody200JSONResponse Example + +func (response RequiredJSONBody200JSONResponse) VisitRequiredJSONBodyResponse(ctx iris.Context) error { + ctx.ResponseWriter().Header().Set("Content-Type", "application/json") + ctx.StatusCode(200) + + return ctx.JSON(&response) +} + +type RequiredJSONBody400Response = BadrequestResponse + +func (response RequiredJSONBody400Response) VisitRequiredJSONBodyResponse(ctx iris.Context) error { + ctx.StatusCode(400) + return nil +} + +type RequiredJSONBodydefaultResponse struct { + StatusCode int +} + +func (response RequiredJSONBodydefaultResponse) VisitRequiredJSONBodyResponse(ctx iris.Context) error { + ctx.StatusCode(response.StatusCode) + return nil +} + +type RequiredTextBodyRequestObject struct { + Body *RequiredTextBodyTextRequestBody +} + +type RequiredTextBodyResponseObject interface { + VisitRequiredTextBodyResponse(ctx iris.Context) error +} + +type RequiredTextBody200TextResponse string + +func (response RequiredTextBody200TextResponse) VisitRequiredTextBodyResponse(ctx iris.Context) error { + ctx.ResponseWriter().Header().Set("Content-Type", "text/plain") + ctx.StatusCode(200) + + _, err := ctx.WriteString(string(response)) + return err +} + +type RequiredTextBody400Response = BadrequestResponse + +func (response RequiredTextBody400Response) VisitRequiredTextBodyResponse(ctx iris.Context) error { + ctx.StatusCode(400) + return nil +} + +type RequiredTextBodydefaultResponse struct { + StatusCode int +} + +func (response RequiredTextBodydefaultResponse) VisitRequiredTextBodyResponse(ctx iris.Context) error { + ctx.StatusCode(response.StatusCode) + return nil +} + +type ReservedGoKeywordParametersRequestObject struct { + Type string `json:"type"` +} + +type ReservedGoKeywordParametersResponseObject interface { + VisitReservedGoKeywordParametersResponse(ctx iris.Context) error +} + +type ReservedGoKeywordParameters200TextResponse string + +func (response ReservedGoKeywordParameters200TextResponse) VisitReservedGoKeywordParametersResponse(ctx iris.Context) error { + ctx.ResponseWriter().Header().Set("Content-Type", "text/plain") + ctx.StatusCode(200) + + _, err := ctx.WriteString(string(response)) + return err +} + +type ReusableResponsesRequestObject struct { + Body *ReusableResponsesJSONRequestBody +} + +type ReusableResponsesResponseObject interface { + VisitReusableResponsesResponse(ctx iris.Context) error +} + +type ReusableResponses200JSONResponse struct{ ReusableresponseJSONResponse } + +func (response ReusableResponses200JSONResponse) VisitReusableResponsesResponse(ctx iris.Context) error { + ctx.ResponseWriter().Header().Set("header1", fmt.Sprint(response.Headers.Header1)) + ctx.ResponseWriter().Header().Set("header2", fmt.Sprint(response.Headers.Header2)) + ctx.ResponseWriter().Header().Set("Content-Type", "application/json") + ctx.StatusCode(200) + + return ctx.JSON(&response.Body) +} + +type ReusableResponses400Response = BadrequestResponse + +func (response ReusableResponses400Response) VisitReusableResponsesResponse(ctx iris.Context) error { + ctx.StatusCode(400) + return nil +} + +type ReusableResponsesdefaultResponse struct { + StatusCode int +} + +func (response ReusableResponsesdefaultResponse) VisitReusableResponsesResponse(ctx iris.Context) error { + ctx.StatusCode(response.StatusCode) + return nil +} + +type TextExampleRequestObject struct { + Body *TextExampleTextRequestBody +} + +type TextExampleResponseObject interface { + VisitTextExampleResponse(ctx iris.Context) error +} + +type TextExample200TextResponse string + +func (response TextExample200TextResponse) VisitTextExampleResponse(ctx iris.Context) error { + ctx.ResponseWriter().Header().Set("Content-Type", "text/plain") + ctx.StatusCode(200) + + _, err := ctx.WriteString(string(response)) + return err +} + +type TextExample400Response = BadrequestResponse + +func (response TextExample400Response) VisitTextExampleResponse(ctx iris.Context) error { + ctx.StatusCode(400) + return nil +} + +type TextExampledefaultResponse struct { + StatusCode int +} + +func (response TextExampledefaultResponse) VisitTextExampleResponse(ctx iris.Context) error { + ctx.StatusCode(response.StatusCode) + return nil +} + +type UnknownExampleRequestObject struct { + Body io.Reader +} + +type UnknownExampleResponseObject interface { + VisitUnknownExampleResponse(ctx iris.Context) error +} + +type UnknownExample200Videomp4Response struct { + Body io.Reader + ContentLength int64 +} + +func (response UnknownExample200Videomp4Response) VisitUnknownExampleResponse(ctx iris.Context) error { + ctx.ResponseWriter().Header().Set("Content-Type", "video/mp4") + if response.ContentLength != 0 { + ctx.ResponseWriter().Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + ctx.StatusCode(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(ctx.ResponseWriter(), response.Body) + return err +} + +type UnknownExample400Response = BadrequestResponse + +func (response UnknownExample400Response) VisitUnknownExampleResponse(ctx iris.Context) error { + ctx.StatusCode(400) + return nil +} + +type UnknownExampledefaultResponse struct { + StatusCode int +} + +func (response UnknownExampledefaultResponse) VisitUnknownExampleResponse(ctx iris.Context) error { + ctx.StatusCode(response.StatusCode) + return nil +} + +type UnspecifiedContentTypeRequestObject struct { + ContentType string + Body io.Reader +} + +type UnspecifiedContentTypeResponseObject interface { + VisitUnspecifiedContentTypeResponse(ctx iris.Context) error +} + +type UnspecifiedContentType200VideoResponse struct { + Body io.Reader + ContentType string + ContentLength int64 +} + +func (response UnspecifiedContentType200VideoResponse) VisitUnspecifiedContentTypeResponse(ctx iris.Context) error { + ctx.ResponseWriter().Header().Set("Content-Type", response.ContentType) + if response.ContentLength != 0 { + ctx.ResponseWriter().Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + ctx.StatusCode(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(ctx.ResponseWriter(), response.Body) + return err +} + +type UnspecifiedContentType400Response = BadrequestResponse + +func (response UnspecifiedContentType400Response) VisitUnspecifiedContentTypeResponse(ctx iris.Context) error { + ctx.StatusCode(400) + return nil +} + +type UnspecifiedContentType401Response struct { +} + +func (response UnspecifiedContentType401Response) VisitUnspecifiedContentTypeResponse(ctx iris.Context) error { + ctx.StatusCode(401) + return nil +} + +type UnspecifiedContentType403Response struct { +} + +func (response UnspecifiedContentType403Response) VisitUnspecifiedContentTypeResponse(ctx iris.Context) error { + ctx.StatusCode(403) + return nil +} + +type UnspecifiedContentTypedefaultResponse struct { + StatusCode int +} + +func (response UnspecifiedContentTypedefaultResponse) VisitUnspecifiedContentTypeResponse(ctx iris.Context) error { + ctx.StatusCode(response.StatusCode) + return nil +} + +type URLEncodedExampleRequestObject struct { + Body *URLEncodedExampleFormdataRequestBody +} + +type URLEncodedExampleResponseObject interface { + VisitURLEncodedExampleResponse(ctx iris.Context) error +} + +type URLEncodedExample200FormdataResponse Example + +func (response URLEncodedExample200FormdataResponse) VisitURLEncodedExampleResponse(ctx iris.Context) error { + ctx.ResponseWriter().Header().Set("Content-Type", "application/x-www-form-urlencoded") + ctx.StatusCode(200) + + if form, err := runtime.MarshalForm(response, nil); err != nil { + return err + } else { + _, err := ctx.WriteString(form.Encode()) + return err + } +} + +type URLEncodedExample400Response = BadrequestResponse + +func (response URLEncodedExample400Response) VisitURLEncodedExampleResponse(ctx iris.Context) error { + ctx.StatusCode(400) + return nil +} + +type URLEncodedExampledefaultResponse struct { + StatusCode int +} + +func (response URLEncodedExampledefaultResponse) VisitURLEncodedExampleResponse(ctx iris.Context) error { + ctx.StatusCode(response.StatusCode) + return nil +} + +type HeadersExampleRequestObject struct { + Params HeadersExampleParams + Body *HeadersExampleJSONRequestBody +} + +type HeadersExampleResponseObject interface { + VisitHeadersExampleResponse(ctx iris.Context) error +} + +type HeadersExample200ResponseHeaders struct { + Header1 string + Header2 int +} + +type HeadersExample200JSONResponse struct { + Body Example + Headers HeadersExample200ResponseHeaders +} + +func (response HeadersExample200JSONResponse) VisitHeadersExampleResponse(ctx iris.Context) error { + ctx.ResponseWriter().Header().Set("header1", fmt.Sprint(response.Headers.Header1)) + ctx.ResponseWriter().Header().Set("header2", fmt.Sprint(response.Headers.Header2)) + ctx.ResponseWriter().Header().Set("Content-Type", "application/json") + ctx.StatusCode(200) + + return ctx.JSON(&response.Body) +} + +type HeadersExample400Response = BadrequestResponse + +func (response HeadersExample400Response) VisitHeadersExampleResponse(ctx iris.Context) error { + ctx.StatusCode(400) + return nil +} + +type HeadersExampledefaultResponse struct { + StatusCode int +} + +func (response HeadersExampledefaultResponse) VisitHeadersExampleResponse(ctx iris.Context) error { + ctx.StatusCode(response.StatusCode) + return nil +} + +type UnionExampleRequestObject struct { + Body *UnionExampleJSONRequestBody +} + +type UnionExampleResponseObject interface { + VisitUnionExampleResponse(ctx iris.Context) error +} + +type UnionExample200ResponseHeaders struct { + Header1 string + Header2 int +} + +type UnionExample200ApplicationAlternativePlusJSONResponse struct { + Body Example + Headers UnionExample200ResponseHeaders +} + +func (response UnionExample200ApplicationAlternativePlusJSONResponse) VisitUnionExampleResponse(ctx iris.Context) error { + ctx.ResponseWriter().Header().Set("header1", fmt.Sprint(response.Headers.Header1)) + ctx.ResponseWriter().Header().Set("header2", fmt.Sprint(response.Headers.Header2)) + ctx.ResponseWriter().Header().Set("Content-Type", "application/alternative+json") + ctx.StatusCode(200) + + return ctx.JSON(&response.Body) +} + +type UnionExample200JSONResponse struct { + Body struct { + union json.RawMessage + } + Headers UnionExample200ResponseHeaders +} + +func (response UnionExample200JSONResponse) VisitUnionExampleResponse(ctx iris.Context) error { + ctx.ResponseWriter().Header().Set("header1", fmt.Sprint(response.Headers.Header1)) + ctx.ResponseWriter().Header().Set("header2", fmt.Sprint(response.Headers.Header2)) + ctx.ResponseWriter().Header().Set("Content-Type", "application/json") + ctx.StatusCode(200) + + return ctx.JSON(&response.Body.union) +} + +type UnionExample400Response = BadrequestResponse + +func (response UnionExample400Response) VisitUnionExampleResponse(ctx iris.Context) error { + ctx.StatusCode(400) + return nil +} + +type UnionExampledefaultResponse struct { + StatusCode int +} + +func (response UnionExampledefaultResponse) VisitUnionExampleResponse(ctx iris.Context) error { + ctx.StatusCode(response.StatusCode) + return nil +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (POST /json) + JSONExample(ctx context.Context, request JSONExampleRequestObject) (JSONExampleResponseObject, error) + + // (POST /multipart) + MultipartExample(ctx context.Context, request MultipartExampleRequestObject) (MultipartExampleResponseObject, error) + + // (POST /multipart-related) + MultipartRelatedExample(ctx context.Context, request MultipartRelatedExampleRequestObject) (MultipartRelatedExampleResponseObject, error) + + // (POST /multiple) + MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) + + // (POST /required-json-body) + RequiredJSONBody(ctx context.Context, request RequiredJSONBodyRequestObject) (RequiredJSONBodyResponseObject, error) + + // (POST /required-text-body) + RequiredTextBody(ctx context.Context, request RequiredTextBodyRequestObject) (RequiredTextBodyResponseObject, error) + + // (GET /reserved-go-keyword-parameters/{type}) + ReservedGoKeywordParameters(ctx context.Context, request ReservedGoKeywordParametersRequestObject) (ReservedGoKeywordParametersResponseObject, error) + + // (POST /reusable-responses) + ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) + + // (POST /text) + TextExample(ctx context.Context, request TextExampleRequestObject) (TextExampleResponseObject, error) + + // (POST /unknown) + UnknownExample(ctx context.Context, request UnknownExampleRequestObject) (UnknownExampleResponseObject, error) + + // (POST /unspecified-content-type) + UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) (UnspecifiedContentTypeResponseObject, error) + + // (POST /urlencoded) + URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) (URLEncodedExampleResponseObject, error) + + // (POST /with-headers) + HeadersExample(ctx context.Context, request HeadersExampleRequestObject) (HeadersExampleResponseObject, error) + + // (POST /with-union) + UnionExample(ctx context.Context, request UnionExampleRequestObject) (UnionExampleResponseObject, error) +} + +type StrictHandlerFunc = strictiris.StrictIrisHandlerFunc +type StrictMiddlewareFunc = strictiris.StrictIrisMiddlewareFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +// JSONExample operation middleware +func (sh *strictHandler) JSONExample(ctx iris.Context) { + var request JSONExampleRequestObject + + var body JSONExampleJSONRequestBody + if err := ctx.ReadJSON(&body); err != nil { + if !errors.Is(err, io.EOF) { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else { + request.Body = &body + } + + handler := func(ctx iris.Context, request interface{}) (interface{}, error) { + return sh.ssi.JSONExample(ctx, request.(JSONExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "JSONExample") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } else if validResponse, ok := response.(JSONExampleResponseObject); ok { + if err := validResponse.VisitJSONExampleResponse(ctx); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else if response != nil { + ctx.Writef("Unexpected response type: %T", response) + return + } +} + +// MultipartExample operation middleware +func (sh *strictHandler) MultipartExample(ctx iris.Context) { + var request MultipartExampleRequestObject + + if reader, err := ctx.Request().MultipartReader(); err == nil { + request.Body = reader + } else { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + + handler := func(ctx iris.Context, request interface{}) (interface{}, error) { + return sh.ssi.MultipartExample(ctx, request.(MultipartExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipartExample") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } else if validResponse, ok := response.(MultipartExampleResponseObject); ok { + if err := validResponse.VisitMultipartExampleResponse(ctx); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else if response != nil { + ctx.Writef("Unexpected response type: %T", response) + return + } +} + +// MultipartRelatedExample operation middleware +func (sh *strictHandler) MultipartRelatedExample(ctx iris.Context) { + var request MultipartRelatedExampleRequestObject + + if _, params, err := mime.ParseMediaType(ctx.Request().Header.Get("Content-Type")); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } else if boundary := params["boundary"]; boundary == "" { + ctx.StopWithError(http.StatusBadRequest, http.ErrMissingBoundary) + return + } else { + request.Body = multipart.NewReader(ctx.Request().Body, boundary) + } + + handler := func(ctx iris.Context, request interface{}) (interface{}, error) { + return sh.ssi.MultipartRelatedExample(ctx, request.(MultipartRelatedExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipartRelatedExample") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } else if validResponse, ok := response.(MultipartRelatedExampleResponseObject); ok { + if err := validResponse.VisitMultipartRelatedExampleResponse(ctx); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else if response != nil { + ctx.Writef("Unexpected response type: %T", response) + return + } +} + +// MultipleRequestAndResponseTypes operation middleware +func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx iris.Context) { + var request MultipleRequestAndResponseTypesRequestObject + + if strings.HasPrefix(ctx.GetHeader("Content-Type"), "application/json") { + + var body MultipleRequestAndResponseTypesJSONRequestBody + if err := ctx.ReadJSON(&body); err != nil { + if !errors.Is(err, io.EOF) { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else { + request.JSONBody = &body + } + } + if strings.HasPrefix(ctx.GetHeader("Content-Type"), "application/x-www-form-urlencoded") { + if err := ctx.Request().ParseForm(); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + var body MultipleRequestAndResponseTypesFormdataRequestBody + if err := runtime.BindForm(&body, ctx.Request().Form, nil, nil); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + request.FormdataBody = &body + } + if strings.HasPrefix(ctx.GetHeader("Content-Type"), "image/png") { + request.Body = ctx.Request().Body + } + if strings.HasPrefix(ctx.GetHeader("Content-Type"), "multipart/form-data") { + if reader, err := ctx.Request().MultipartReader(); err == nil { + request.MultipartBody = reader + } else { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } + if strings.HasPrefix(ctx.GetHeader("Content-Type"), "text/plain") { + data, err := io.ReadAll(ctx.Request().Body) + if err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + if len(data) > 0 { + body := MultipleRequestAndResponseTypesTextRequestBody(data) + request.TextBody = &body + } + } + + handler := func(ctx iris.Context, request interface{}) (interface{}, error) { + return sh.ssi.MultipleRequestAndResponseTypes(ctx, request.(MultipleRequestAndResponseTypesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipleRequestAndResponseTypes") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } else if validResponse, ok := response.(MultipleRequestAndResponseTypesResponseObject); ok { + if err := validResponse.VisitMultipleRequestAndResponseTypesResponse(ctx); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else if response != nil { + ctx.Writef("Unexpected response type: %T", response) + return + } +} + +// RequiredJSONBody operation middleware +func (sh *strictHandler) RequiredJSONBody(ctx iris.Context) { + var request RequiredJSONBodyRequestObject + + var body RequiredJSONBodyJSONRequestBody + if err := ctx.ReadJSON(&body); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + request.Body = &body + + handler := func(ctx iris.Context, request interface{}) (interface{}, error) { + return sh.ssi.RequiredJSONBody(ctx, request.(RequiredJSONBodyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "RequiredJSONBody") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } else if validResponse, ok := response.(RequiredJSONBodyResponseObject); ok { + if err := validResponse.VisitRequiredJSONBodyResponse(ctx); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else if response != nil { + ctx.Writef("Unexpected response type: %T", response) + return + } +} + +// RequiredTextBody operation middleware +func (sh *strictHandler) RequiredTextBody(ctx iris.Context) { + var request RequiredTextBodyRequestObject + + data, err := io.ReadAll(ctx.Request().Body) + if err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + body := RequiredTextBodyTextRequestBody(data) + request.Body = &body + + handler := func(ctx iris.Context, request interface{}) (interface{}, error) { + return sh.ssi.RequiredTextBody(ctx, request.(RequiredTextBodyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "RequiredTextBody") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } else if validResponse, ok := response.(RequiredTextBodyResponseObject); ok { + if err := validResponse.VisitRequiredTextBodyResponse(ctx); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else if response != nil { + ctx.Writef("Unexpected response type: %T", response) + return + } +} + +// ReservedGoKeywordParameters operation middleware +func (sh *strictHandler) ReservedGoKeywordParameters(ctx iris.Context, pType string) { + var request ReservedGoKeywordParametersRequestObject + + request.Type = pType + + handler := func(ctx iris.Context, request interface{}) (interface{}, error) { + return sh.ssi.ReservedGoKeywordParameters(ctx, request.(ReservedGoKeywordParametersRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReservedGoKeywordParameters") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } else if validResponse, ok := response.(ReservedGoKeywordParametersResponseObject); ok { + if err := validResponse.VisitReservedGoKeywordParametersResponse(ctx); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else if response != nil { + ctx.Writef("Unexpected response type: %T", response) + return + } +} + +// ReusableResponses operation middleware +func (sh *strictHandler) ReusableResponses(ctx iris.Context) { + var request ReusableResponsesRequestObject + + var body ReusableResponsesJSONRequestBody + if err := ctx.ReadJSON(&body); err != nil { + if !errors.Is(err, io.EOF) { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else { + request.Body = &body + } + + handler := func(ctx iris.Context, request interface{}) (interface{}, error) { + return sh.ssi.ReusableResponses(ctx, request.(ReusableResponsesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReusableResponses") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } else if validResponse, ok := response.(ReusableResponsesResponseObject); ok { + if err := validResponse.VisitReusableResponsesResponse(ctx); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else if response != nil { + ctx.Writef("Unexpected response type: %T", response) + return + } +} + +// TextExample operation middleware +func (sh *strictHandler) TextExample(ctx iris.Context) { + var request TextExampleRequestObject + + data, err := io.ReadAll(ctx.Request().Body) + if err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + if len(data) > 0 { + body := TextExampleTextRequestBody(data) + request.Body = &body + } + + handler := func(ctx iris.Context, request interface{}) (interface{}, error) { + return sh.ssi.TextExample(ctx, request.(TextExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "TextExample") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } else if validResponse, ok := response.(TextExampleResponseObject); ok { + if err := validResponse.VisitTextExampleResponse(ctx); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else if response != nil { + ctx.Writef("Unexpected response type: %T", response) + return + } +} + +// UnknownExample operation middleware +func (sh *strictHandler) UnknownExample(ctx iris.Context) { + var request UnknownExampleRequestObject + + request.Body = ctx.Request().Body + + handler := func(ctx iris.Context, request interface{}) (interface{}, error) { + return sh.ssi.UnknownExample(ctx, request.(UnknownExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnknownExample") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } else if validResponse, ok := response.(UnknownExampleResponseObject); ok { + if err := validResponse.VisitUnknownExampleResponse(ctx); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else if response != nil { + ctx.Writef("Unexpected response type: %T", response) + return + } +} + +// UnspecifiedContentType operation middleware +func (sh *strictHandler) UnspecifiedContentType(ctx iris.Context) { + var request UnspecifiedContentTypeRequestObject + + request.ContentType = ctx.GetContentTypeRequested() + + request.Body = ctx.Request().Body + + handler := func(ctx iris.Context, request interface{}) (interface{}, error) { + return sh.ssi.UnspecifiedContentType(ctx, request.(UnspecifiedContentTypeRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnspecifiedContentType") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } else if validResponse, ok := response.(UnspecifiedContentTypeResponseObject); ok { + if err := validResponse.VisitUnspecifiedContentTypeResponse(ctx); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else if response != nil { + ctx.Writef("Unexpected response type: %T", response) + return + } +} + +// URLEncodedExample operation middleware +func (sh *strictHandler) URLEncodedExample(ctx iris.Context) { + var request URLEncodedExampleRequestObject + + if err := ctx.Request().ParseForm(); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + var body URLEncodedExampleFormdataRequestBody + if err := runtime.BindForm(&body, ctx.Request().Form, nil, nil); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + request.Body = &body + + handler := func(ctx iris.Context, request interface{}) (interface{}, error) { + return sh.ssi.URLEncodedExample(ctx, request.(URLEncodedExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "URLEncodedExample") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } else if validResponse, ok := response.(URLEncodedExampleResponseObject); ok { + if err := validResponse.VisitURLEncodedExampleResponse(ctx); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else if response != nil { + ctx.Writef("Unexpected response type: %T", response) + return + } +} + +// HeadersExample operation middleware +func (sh *strictHandler) HeadersExample(ctx iris.Context, params HeadersExampleParams) { + var request HeadersExampleRequestObject + + request.Params = params + + var body HeadersExampleJSONRequestBody + if err := ctx.ReadJSON(&body); err != nil { + if !errors.Is(err, io.EOF) { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else { + request.Body = &body + } + + handler := func(ctx iris.Context, request interface{}) (interface{}, error) { + return sh.ssi.HeadersExample(ctx, request.(HeadersExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "HeadersExample") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } else if validResponse, ok := response.(HeadersExampleResponseObject); ok { + if err := validResponse.VisitHeadersExampleResponse(ctx); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else if response != nil { + ctx.Writef("Unexpected response type: %T", response) + return + } +} + +// UnionExample operation middleware +func (sh *strictHandler) UnionExample(ctx iris.Context) { + var request UnionExampleRequestObject + + var body UnionExampleJSONRequestBody + if err := ctx.ReadJSON(&body); err != nil { + if !errors.Is(err, io.EOF) { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else { + request.Body = &body + } + + handler := func(ctx iris.Context, request interface{}) (interface{}, error) { + return sh.ssi.UnionExample(ctx, request.(UnionExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnionExample") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } else if validResponse, ok := response.(UnionExampleResponseObject); ok { + if err := validResponse.VisitUnionExampleResponse(ctx); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else if response != nil { + ctx.Writef("Unexpected response type: %T", response) + return + } +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/+xZTXPbNhP+Kzt431NKmrbjE2+NJ5O2aeuObJ86PkDESkJCAiiwFK3R6L93QEBfFq1K", + "qT4ynt5Ecr/wPLuLBTRlha6MVqjIsXzKLDqjlcP2oc+Fxb9qdOSfBLrCSkNSK5azD1z04rdZwizWjvdL", + "nKt7+UIrQtWqcmNKWXCvmn1xXn/KXDHCivtf/7c4YDn7X7YMJQtfXYbPvDIlstlslryI4O4zS9gIuUDb", + "Rht+Xq3bpolBljNHVqoh80aC2HWnmFSEQ7TemxeNQXiBeRz5lBmrDVqSAaMxL2vs9hTf6P4XLCisQKqB", + "3sTyViviUjkQcjBAi4ogggfehgNXG6MtoYD+BLyHgsChHaNlCSNJPjB2v/oeYsCOJWyM1gVHVxeXF5ee", + "L21QcSNZzt63rxJmOI3aBS0IMrqL91/u734H6YDXpCtOsuBlOYGKWzfiJQqQirQPsS7IXbDWk22J/1lE", + "7Y8RSp81bQJ90GJyjIRp83Ilna8vL0+Ul7OE3QRnXTYWQWUrBdaaGfC67MD8UX1VulGA1mobV5ZVdUnS", + "cEurXK2j/dtcZBfIF/aygbZVKjjxI6F+KE/nBj61WHJCsQMBvSC5Hw8r5o/Kwr/xc1YOYj/u7FP3I904", + "GOkGSINAXkIjaQRzxRcNVirg4KQalgjzoJJOMkuM296PSvTiWh68jaP3s2TNynPaNE3aFlBtS1SFFt9G", + "YcJkxYeYGTVcV/e2ObGc9SfkU3ZzgztQISeM8JkyU3Kptu/eJ2rp/yF9sMIO5eofpUWRekbSfqyP7sKN", + "5QVeyg8ac90EnIZKOl+l4aMb6boUwMuGT1zoD5sTRy+q+8mjLcyjjx3BgffJcrI1vu0xZEGtz6zzUPuA", + "z/SP1O6R+PvSd+qa2p+i9kwg0qFOv+Kk0VakhlteIaF12dTHOfO2hthh8o+FJBRcQR9B8QoF8AGhhU8a", + "oknXwU/w+0l/DiJLU+2BY/GQ/zllHrz2EMIS5h2wPOD3ko5kC8BPx6VqjmY46qZrrl5L+Cgyh87iwPmB", + "pIvjDvyCp96KxHmOTNtzc+Pwf4qk9ky+Pnj7lrDLsH3AweN77wJ1ePk6ZlFrF9i+cY7ZAcWxFKizytzs", + "aflsoDqDhRxIFGlcRRpie60l3GpVWKT1A4jfDJUmWBiD/gRohBAQaPfHBqGqHYHhzoGktouUMtwVCdxo", + "Ho/LyG6Dp4dlO93G6rsjcfruXIzeXF7tr/L+yHmzdpB4pR57v34MMvvemB3sxLLneetwfs9Uzo2kUbpy", + "pdxdwj8FgeWeXqAc+4lICbBItVUoYCz5/Bp0ozajgSWtXbNQCGM5Dc2vt/cZiJKttq5Zsu0K/OkNX9Ae", + "84+DU+VpreS2i/pH/xniDP1yb5BafafX8LwktIqTHOMPh7m/2bSiFd4N2kp7wXKyo4ent5dVs4SFf45C", + "C6pt6fsEkcmzLPzjdOEaPhyivZA640Z6FP4OAAD//0nTejA+HAAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/strict-server/iris/server.go b/internal/test/strict-server/iris/server.go new file mode 100644 index 0000000000..f87df99759 --- /dev/null +++ b/internal/test/strict-server/iris/server.go @@ -0,0 +1,151 @@ +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=server.cfg.yaml ../strict-schema.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=types.cfg.yaml ../strict-schema.yaml + +package api + +import ( + "context" + "encoding/json" + "io" + "mime/multipart" +) + +type StrictServer struct{} + +func (s StrictServer) JSONExample(ctx context.Context, request JSONExampleRequestObject) (JSONExampleResponseObject, error) { + return JSONExample200JSONResponse(*request.Body), nil +} + +func (s StrictServer) MultipartExample(ctx context.Context, request MultipartExampleRequestObject) (MultipartExampleResponseObject, error) { + return MultipartExample200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.Body.NextPart() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + w, err := writer.CreatePart(part.Header) + if err != nil { + return err + } + _, err = io.Copy(w, part) + if err != nil { + return err + } + if err = part.Close(); err != nil { + return err + } + } + }), nil +} + +func (s StrictServer) MultipartRelatedExample(ctx context.Context, request MultipartRelatedExampleRequestObject) (MultipartRelatedExampleResponseObject, error) { + return MultipartRelatedExample200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.Body.NextPart() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + w, err := writer.CreatePart(part.Header) + if err != nil { + return err + } + _, err = io.Copy(w, part) + if err != nil { + return err + } + if err = part.Close(); err != nil { + return err + } + } + }), nil +} + +func (s StrictServer) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) { + switch { + case request.Body != nil: + return MultipleRequestAndResponseTypes200ImagepngResponse{Body: request.Body}, nil + case request.JSONBody != nil: + return MultipleRequestAndResponseTypes200JSONResponse(*request.JSONBody), nil + case request.FormdataBody != nil: + return MultipleRequestAndResponseTypes200FormdataResponse(*request.FormdataBody), nil + case request.TextBody != nil: + return MultipleRequestAndResponseTypes200TextResponse(*request.TextBody), nil + case request.MultipartBody != nil: + return MultipleRequestAndResponseTypes200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.MultipartBody.NextPart() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + w, err := writer.CreatePart(part.Header) + if err != nil { + return err + } + _, err = io.Copy(w, part) + if err != nil { + return err + } + if err = part.Close(); err != nil { + return err + } + } + }), nil + default: + return MultipleRequestAndResponseTypes400Response{}, nil + } +} + +func (s StrictServer) TextExample(ctx context.Context, request TextExampleRequestObject) (TextExampleResponseObject, error) { + return TextExample200TextResponse(*request.Body), nil +} + +func (s StrictServer) UnknownExample(ctx context.Context, request UnknownExampleRequestObject) (UnknownExampleResponseObject, error) { + return UnknownExample200Videomp4Response{Body: request.Body}, nil +} + +func (s StrictServer) UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) (UnspecifiedContentTypeResponseObject, error) { + return UnspecifiedContentType200VideoResponse{Body: request.Body, ContentType: request.ContentType}, nil +} + +func (s StrictServer) URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) (URLEncodedExampleResponseObject, error) { + return URLEncodedExample200FormdataResponse(*request.Body), nil +} + +func (s StrictServer) HeadersExample(ctx context.Context, request HeadersExampleRequestObject) (HeadersExampleResponseObject, error) { + return HeadersExample200JSONResponse{Body: *request.Body, Headers: HeadersExample200ResponseHeaders{Header1: request.Params.Header1, Header2: *request.Params.Header2}}, nil +} + +func (s StrictServer) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) { + return ReusableResponses200JSONResponse{ReusableresponseJSONResponse: ReusableresponseJSONResponse{Body: *request.Body}}, nil +} + +func (s StrictServer) RequiredJSONBody(ctx context.Context, request RequiredJSONBodyRequestObject) (RequiredJSONBodyResponseObject, error) { + return RequiredJSONBody200JSONResponse(*request.Body), nil +} + +func (s StrictServer) RequiredTextBody(ctx context.Context, request RequiredTextBodyRequestObject) (RequiredTextBodyResponseObject, error) { + return RequiredTextBody200TextResponse(*request.Body), nil +} + +func (s StrictServer) ReservedGoKeywordParameters(ctx context.Context, request ReservedGoKeywordParametersRequestObject) (ReservedGoKeywordParametersResponseObject, error) { + return ReservedGoKeywordParameters200TextResponse(""), nil +} + +func (s StrictServer) UnionExample(ctx context.Context, request UnionExampleRequestObject) (UnionExampleResponseObject, error) { + union, err := json.Marshal(*request.Body) + if err != nil { + return nil, err + } + + return UnionExample200JSONResponse{ + Body: struct{ union json.RawMessage }{ + union: union, + }, + }, nil +} diff --git a/internal/test/strict-server/iris/types.cfg.yaml b/internal/test/strict-server/iris/types.cfg.yaml new file mode 100644 index 0000000000..2ed9740ea7 --- /dev/null +++ b/internal/test/strict-server/iris/types.cfg.yaml @@ -0,0 +1,5 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +generate: + models: true +output: types.gen.go diff --git a/internal/test/strict-server/iris/types.gen.go b/internal/test/strict-server/iris/types.gen.go new file mode 100644 index 0000000000..fac014c4bb --- /dev/null +++ b/internal/test/strict-server/iris/types.gen.go @@ -0,0 +1,69 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +// Example defines model for example. +type Example struct { + Value *string `json:"value,omitempty"` +} + +// Reusableresponse defines model for reusableresponse. +type Reusableresponse = Example + +// MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesTextBody = string + +// RequiredTextBodyTextBody defines parameters for RequiredTextBody. +type RequiredTextBodyTextBody = string + +// TextExampleTextBody defines parameters for TextExample. +type TextExampleTextBody = string + +// HeadersExampleParams defines parameters for HeadersExample. +type HeadersExampleParams struct { + Header1 string `json:"header1"` + Header2 *int `json:"header2,omitempty"` +} + +// JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. +type JSONExampleJSONRequestBody = Example + +// MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. +type MultipartExampleMultipartRequestBody = Example + +// MultipartRelatedExampleMultipartRequestBody defines body for MultipartRelatedExample for multipart/related ContentType. +type MultipartRelatedExampleMultipartRequestBody = Example + +// MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. +type MultipleRequestAndResponseTypesJSONRequestBody = Example + +// MultipleRequestAndResponseTypesFormdataRequestBody defines body for MultipleRequestAndResponseTypes for application/x-www-form-urlencoded ContentType. +type MultipleRequestAndResponseTypesFormdataRequestBody = Example + +// MultipleRequestAndResponseTypesMultipartRequestBody defines body for MultipleRequestAndResponseTypes for multipart/form-data ContentType. +type MultipleRequestAndResponseTypesMultipartRequestBody = Example + +// MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. +type MultipleRequestAndResponseTypesTextRequestBody = MultipleRequestAndResponseTypesTextBody + +// RequiredJSONBodyJSONRequestBody defines body for RequiredJSONBody for application/json ContentType. +type RequiredJSONBodyJSONRequestBody = Example + +// RequiredTextBodyTextRequestBody defines body for RequiredTextBody for text/plain ContentType. +type RequiredTextBodyTextRequestBody = RequiredTextBodyTextBody + +// ReusableResponsesJSONRequestBody defines body for ReusableResponses for application/json ContentType. +type ReusableResponsesJSONRequestBody = Example + +// TextExampleTextRequestBody defines body for TextExample for text/plain ContentType. +type TextExampleTextRequestBody = TextExampleTextBody + +// URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. +type URLEncodedExampleFormdataRequestBody = Example + +// HeadersExampleJSONRequestBody defines body for HeadersExample for application/json ContentType. +type HeadersExampleJSONRequestBody = Example + +// UnionExampleJSONRequestBody defines body for UnionExample for application/json ContentType. +type UnionExampleJSONRequestBody = Example diff --git a/internal/test/strict-server/stdhttp/Makefile b/internal/test/strict-server/stdhttp/Makefile new file mode 100644 index 0000000000..5ec0edd058 --- /dev/null +++ b/internal/test/strict-server/stdhttp/Makefile @@ -0,0 +1,17 @@ +lint: + $(GOBIN)/golangci-lint run ./... + +lint-ci: + $(GOBIN)/golangci-lint run ./... --output.text.path=stdout --timeout=5m + +generate: + go generate ./... + +test: + go test -cover ./... + +tidy: + go mod tidy + +tidy-ci: + tidied -verbose diff --git a/internal/test/strict-server/stdhttp/go.mod b/internal/test/strict-server/stdhttp/go.mod new file mode 100644 index 0000000000..19a1b1cfb5 --- /dev/null +++ b/internal/test/strict-server/stdhttp/go.mod @@ -0,0 +1,42 @@ +module github.com/oapi-codegen/oapi-codegen/v2/internal/test/strict-server/stdhttp + +go 1.24.3 + +replace github.com/oapi-codegen/oapi-codegen/v2 => ../../../../ + +replace github.com/oapi-codegen/oapi-codegen/v2/internal/test => ../.. + +require ( + github.com/getkin/kin-openapi v0.133.0 + github.com/oapi-codegen/oapi-codegen/v2 v2.0.0-00010101000000-000000000000 + github.com/oapi-codegen/oapi-codegen/v2/internal/test v0.0.0-00010101000000-000000000000 + github.com/oapi-codegen/runtime v1.2.0 + github.com/oapi-codegen/testutil v1.1.0 + github.com/stretchr/testify v1.11.1 +) + +require ( + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/speakeasy-api/jsonpath v0.6.0 // indirect + github.com/speakeasy-api/openapi-overlay v0.10.3 // indirect + github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/tools v0.42.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/internal/test/strict-server/stdhttp/go.sum b/internal/test/strict-server/stdhttp/go.sum new file mode 100644 index 0000000000..4680d9a4bb --- /dev/null +++ b/internal/test/strict-server/stdhttp/go.sum @@ -0,0 +1,185 @@ +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oapi-codegen/runtime v1.2.0 h1:RvKc1CVS1QeKSNzO97FBQbSMZyQ8s6rZd+LpmzwHMP4= +github.com/oapi-codegen/runtime v1.2.0/go.mod h1:Y7ZhmmlE8ikZOmuHRRndiIm7nf3xcVv+YMweKgG1DT0= +github.com/oapi-codegen/testutil v1.1.0 h1:EufqpNg43acR3qzr3ObhXmWg3Sl2kwtRnUN5GYY4d5g= +github.com/oapi-codegen/testutil v1.1.0/go.mod h1:ttCaYbHvJtHuiyeBF0tPIX+4uhEPTeizXKx28okijLw= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8= +github.com/speakeasy-api/jsonpath v0.6.0/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw= +github.com/speakeasy-api/openapi-overlay v0.10.3 h1:70een4vwHyslIp796vM+ox6VISClhtXsCjrQNhxwvWs= +github.com/speakeasy-api/openapi-overlay v0.10.3/go.mod h1:RJjV0jbUHqXLS0/Mxv5XE7LAnJHqHw+01RDdpoGqiyY= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= +github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/test/strict-server/stdhttp/server.cfg.yaml b/internal/test/strict-server/stdhttp/server.cfg.yaml new file mode 100644 index 0000000000..fb4ed1a461 --- /dev/null +++ b/internal/test/strict-server/stdhttp/server.cfg.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +generate: + std-http-server: true + strict-server: true + embedded-spec: true +output: server.gen.go diff --git a/internal/test/strict-server/stdhttp/server.gen.go b/internal/test/strict-server/stdhttp/server.gen.go new file mode 100644 index 0000000000..2d6b8642ee --- /dev/null +++ b/internal/test/strict-server/stdhttp/server.gen.go @@ -0,0 +1,1730 @@ +//go:build go1.22 + +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "mime" + "mime/multipart" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/oapi-codegen/runtime" + strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (POST /json) + JSONExample(w http.ResponseWriter, r *http.Request) + + // (POST /multipart) + MultipartExample(w http.ResponseWriter, r *http.Request) + + // (POST /multipart-related) + MultipartRelatedExample(w http.ResponseWriter, r *http.Request) + + // (POST /multiple) + MultipleRequestAndResponseTypes(w http.ResponseWriter, r *http.Request) + + // (POST /required-json-body) + RequiredJSONBody(w http.ResponseWriter, r *http.Request) + + // (POST /required-text-body) + RequiredTextBody(w http.ResponseWriter, r *http.Request) + + // (GET /reserved-go-keyword-parameters/{type}) + ReservedGoKeywordParameters(w http.ResponseWriter, r *http.Request, pType string) + + // (POST /reusable-responses) + ReusableResponses(w http.ResponseWriter, r *http.Request) + + // (POST /text) + TextExample(w http.ResponseWriter, r *http.Request) + + // (POST /unknown) + UnknownExample(w http.ResponseWriter, r *http.Request) + + // (POST /unspecified-content-type) + UnspecifiedContentType(w http.ResponseWriter, r *http.Request) + + // (POST /urlencoded) + URLEncodedExample(w http.ResponseWriter, r *http.Request) + + // (POST /with-headers) + HeadersExample(w http.ResponseWriter, r *http.Request, params HeadersExampleParams) + + // (POST /with-union) + UnionExample(w http.ResponseWriter, r *http.Request) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// JSONExample operation middleware +func (siw *ServerInterfaceWrapper) JSONExample(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.JSONExample(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// MultipartExample operation middleware +func (siw *ServerInterfaceWrapper) MultipartExample(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.MultipartExample(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// MultipartRelatedExample operation middleware +func (siw *ServerInterfaceWrapper) MultipartRelatedExample(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.MultipartRelatedExample(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// MultipleRequestAndResponseTypes operation middleware +func (siw *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.MultipleRequestAndResponseTypes(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// RequiredJSONBody operation middleware +func (siw *ServerInterfaceWrapper) RequiredJSONBody(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.RequiredJSONBody(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// RequiredTextBody operation middleware +func (siw *ServerInterfaceWrapper) RequiredTextBody(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.RequiredTextBody(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ReservedGoKeywordParameters operation middleware +func (siw *ServerInterfaceWrapper) ReservedGoKeywordParameters(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "type" ------------- + var pType string + + err = runtime.BindStyledParameterWithOptions("simple", "type", r.PathValue("type"), &pType, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "type", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ReservedGoKeywordParameters(w, r, pType) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ReusableResponses operation middleware +func (siw *ServerInterfaceWrapper) ReusableResponses(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ReusableResponses(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// TextExample operation middleware +func (siw *ServerInterfaceWrapper) TextExample(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.TextExample(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// UnknownExample operation middleware +func (siw *ServerInterfaceWrapper) UnknownExample(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UnknownExample(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// UnspecifiedContentType operation middleware +func (siw *ServerInterfaceWrapper) UnspecifiedContentType(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UnspecifiedContentType(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// URLEncodedExample operation middleware +func (siw *ServerInterfaceWrapper) URLEncodedExample(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.URLEncodedExample(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// HeadersExample operation middleware +func (siw *ServerInterfaceWrapper) HeadersExample(w http.ResponseWriter, r *http.Request) { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params HeadersExampleParams + + headers := r.Header + + // ------------- Required header parameter "header1" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("header1")]; found { + var Header1 string + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "header1", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "header1", valueList[0], &Header1, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "header1", Err: err}) + return + } + + params.Header1 = Header1 + + } else { + err := fmt.Errorf("Header parameter header1 is required, but not found") + siw.ErrorHandlerFunc(w, r, &RequiredHeaderError{ParamName: "header1", Err: err}) + return + } + + // ------------- Optional header parameter "header2" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("header2")]; found { + var Header2 int + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "header2", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "header2", valueList[0], &Header2, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false, Type: "integer", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "header2", Err: err}) + return + } + + params.Header2 = &Header2 + + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.HeadersExample(w, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// UnionExample operation middleware +func (siw *ServerInterfaceWrapper) UnionExample(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UnionExample(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{}) +} + +// ServeMux is an abstraction of http.ServeMux. +type ServeMux interface { + HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) + ServeHTTP(w http.ResponseWriter, r *http.Request) +} + +type StdHTTPServerOptions struct { + BaseURL string + BaseRouter ServeMux + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, m ServeMux) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{ + BaseRouter: m, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, m ServeMux, baseURL string) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{ + BaseURL: baseURL, + BaseRouter: m, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.Handler { + m := options.BaseRouter + + if m == nil { + m = http.NewServeMux() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + m.HandleFunc("POST "+options.BaseURL+"/json", wrapper.JSONExample) + m.HandleFunc("POST "+options.BaseURL+"/multipart", wrapper.MultipartExample) + m.HandleFunc("POST "+options.BaseURL+"/multipart-related", wrapper.MultipartRelatedExample) + m.HandleFunc("POST "+options.BaseURL+"/multiple", wrapper.MultipleRequestAndResponseTypes) + m.HandleFunc("POST "+options.BaseURL+"/required-json-body", wrapper.RequiredJSONBody) + m.HandleFunc("POST "+options.BaseURL+"/required-text-body", wrapper.RequiredTextBody) + m.HandleFunc("GET "+options.BaseURL+"/reserved-go-keyword-parameters/{type}", wrapper.ReservedGoKeywordParameters) + m.HandleFunc("POST "+options.BaseURL+"/reusable-responses", wrapper.ReusableResponses) + m.HandleFunc("POST "+options.BaseURL+"/text", wrapper.TextExample) + m.HandleFunc("POST "+options.BaseURL+"/unknown", wrapper.UnknownExample) + m.HandleFunc("POST "+options.BaseURL+"/unspecified-content-type", wrapper.UnspecifiedContentType) + m.HandleFunc("POST "+options.BaseURL+"/urlencoded", wrapper.URLEncodedExample) + m.HandleFunc("POST "+options.BaseURL+"/with-headers", wrapper.HeadersExample) + m.HandleFunc("POST "+options.BaseURL+"/with-union", wrapper.UnionExample) + + return m +} + +type BadrequestResponse struct { +} + +type ReusableresponseResponseHeaders struct { + Header1 string + Header2 int +} +type ReusableresponseJSONResponse struct { + Body Example + + Headers ReusableresponseResponseHeaders +} + +type JSONExampleRequestObject struct { + Body *JSONExampleJSONRequestBody +} + +type JSONExampleResponseObject interface { + VisitJSONExampleResponse(w http.ResponseWriter) error +} + +type JSONExample200JSONResponse Example + +func (response JSONExample200JSONResponse) VisitJSONExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type JSONExample400Response = BadrequestResponse + +func (response JSONExample400Response) VisitJSONExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type JSONExampledefaultResponse struct { + StatusCode int +} + +func (response JSONExampledefaultResponse) VisitJSONExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type MultipartExampleRequestObject struct { + Body *multipart.Reader +} + +type MultipartExampleResponseObject interface { + VisitMultipartExampleResponse(w http.ResponseWriter) error +} + +type MultipartExample200MultipartResponse func(writer *multipart.Writer) error + +func (response MultipartExample200MultipartResponse) VisitMultipartExampleResponse(w http.ResponseWriter) error { + writer := multipart.NewWriter(w) + w.Header().Set("Content-Type", writer.FormDataContentType()) + w.WriteHeader(200) + + defer writer.Close() + return response(writer) +} + +type MultipartExample400Response = BadrequestResponse + +func (response MultipartExample400Response) VisitMultipartExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type MultipartExampledefaultResponse struct { + StatusCode int +} + +func (response MultipartExampledefaultResponse) VisitMultipartExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type MultipartRelatedExampleRequestObject struct { + Body *multipart.Reader +} + +type MultipartRelatedExampleResponseObject interface { + VisitMultipartRelatedExampleResponse(w http.ResponseWriter) error +} + +type MultipartRelatedExample200MultipartResponse func(writer *multipart.Writer) error + +func (response MultipartRelatedExample200MultipartResponse) VisitMultipartRelatedExampleResponse(w http.ResponseWriter) error { + writer := multipart.NewWriter(w) + w.Header().Set("Content-Type", mime.FormatMediaType("multipart/related", map[string]string{"boundary": writer.Boundary()})) + w.WriteHeader(200) + + defer writer.Close() + return response(writer) +} + +type MultipartRelatedExample400Response = BadrequestResponse + +func (response MultipartRelatedExample400Response) VisitMultipartRelatedExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type MultipartRelatedExampledefaultResponse struct { + StatusCode int +} + +func (response MultipartRelatedExampledefaultResponse) VisitMultipartRelatedExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type MultipleRequestAndResponseTypesRequestObject struct { + JSONBody *MultipleRequestAndResponseTypesJSONRequestBody + FormdataBody *MultipleRequestAndResponseTypesFormdataRequestBody + Body io.Reader + MultipartBody *multipart.Reader + TextBody *MultipleRequestAndResponseTypesTextRequestBody +} + +type MultipleRequestAndResponseTypesResponseObject interface { + VisitMultipleRequestAndResponseTypesResponse(w http.ResponseWriter) error +} + +type MultipleRequestAndResponseTypes200JSONResponse Example + +func (response MultipleRequestAndResponseTypes200JSONResponse) VisitMultipleRequestAndResponseTypesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type MultipleRequestAndResponseTypes200FormdataResponse Example + +func (response MultipleRequestAndResponseTypes200FormdataResponse) VisitMultipleRequestAndResponseTypesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/x-www-form-urlencoded") + w.WriteHeader(200) + + if form, err := runtime.MarshalForm(response, nil); err != nil { + return err + } else { + _, err := w.Write([]byte(form.Encode())) + return err + } +} + +type MultipleRequestAndResponseTypes200ImagepngResponse struct { + Body io.Reader + ContentLength int64 +} + +func (response MultipleRequestAndResponseTypes200ImagepngResponse) VisitMultipleRequestAndResponseTypesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "image/png") + if response.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + w.WriteHeader(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(w, response.Body) + return err +} + +type MultipleRequestAndResponseTypes200MultipartResponse func(writer *multipart.Writer) error + +func (response MultipleRequestAndResponseTypes200MultipartResponse) VisitMultipleRequestAndResponseTypesResponse(w http.ResponseWriter) error { + writer := multipart.NewWriter(w) + w.Header().Set("Content-Type", writer.FormDataContentType()) + w.WriteHeader(200) + + defer writer.Close() + return response(writer) +} + +type MultipleRequestAndResponseTypes200TextResponse string + +func (response MultipleRequestAndResponseTypes200TextResponse) VisitMultipleRequestAndResponseTypesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(200) + + _, err := w.Write([]byte(response)) + return err +} + +type MultipleRequestAndResponseTypes400Response = BadrequestResponse + +func (response MultipleRequestAndResponseTypes400Response) VisitMultipleRequestAndResponseTypesResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type RequiredJSONBodyRequestObject struct { + Body *RequiredJSONBodyJSONRequestBody +} + +type RequiredJSONBodyResponseObject interface { + VisitRequiredJSONBodyResponse(w http.ResponseWriter) error +} + +type RequiredJSONBody200JSONResponse Example + +func (response RequiredJSONBody200JSONResponse) VisitRequiredJSONBodyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type RequiredJSONBody400Response = BadrequestResponse + +func (response RequiredJSONBody400Response) VisitRequiredJSONBodyResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type RequiredJSONBodydefaultResponse struct { + StatusCode int +} + +func (response RequiredJSONBodydefaultResponse) VisitRequiredJSONBodyResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type RequiredTextBodyRequestObject struct { + Body *RequiredTextBodyTextRequestBody +} + +type RequiredTextBodyResponseObject interface { + VisitRequiredTextBodyResponse(w http.ResponseWriter) error +} + +type RequiredTextBody200TextResponse string + +func (response RequiredTextBody200TextResponse) VisitRequiredTextBodyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(200) + + _, err := w.Write([]byte(response)) + return err +} + +type RequiredTextBody400Response = BadrequestResponse + +func (response RequiredTextBody400Response) VisitRequiredTextBodyResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type RequiredTextBodydefaultResponse struct { + StatusCode int +} + +func (response RequiredTextBodydefaultResponse) VisitRequiredTextBodyResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type ReservedGoKeywordParametersRequestObject struct { + Type string `json:"type"` +} + +type ReservedGoKeywordParametersResponseObject interface { + VisitReservedGoKeywordParametersResponse(w http.ResponseWriter) error +} + +type ReservedGoKeywordParameters200TextResponse string + +func (response ReservedGoKeywordParameters200TextResponse) VisitReservedGoKeywordParametersResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(200) + + _, err := w.Write([]byte(response)) + return err +} + +type ReusableResponsesRequestObject struct { + Body *ReusableResponsesJSONRequestBody +} + +type ReusableResponsesResponseObject interface { + VisitReusableResponsesResponse(w http.ResponseWriter) error +} + +type ReusableResponses200JSONResponse struct{ ReusableresponseJSONResponse } + +func (response ReusableResponses200JSONResponse) VisitReusableResponsesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) + w.Header().Set("header2", fmt.Sprint(response.Headers.Header2)) + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response.Body) +} + +type ReusableResponses400Response = BadrequestResponse + +func (response ReusableResponses400Response) VisitReusableResponsesResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type ReusableResponsesdefaultResponse struct { + StatusCode int +} + +func (response ReusableResponsesdefaultResponse) VisitReusableResponsesResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type TextExampleRequestObject struct { + Body *TextExampleTextRequestBody +} + +type TextExampleResponseObject interface { + VisitTextExampleResponse(w http.ResponseWriter) error +} + +type TextExample200TextResponse string + +func (response TextExample200TextResponse) VisitTextExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(200) + + _, err := w.Write([]byte(response)) + return err +} + +type TextExample400Response = BadrequestResponse + +func (response TextExample400Response) VisitTextExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type TextExampledefaultResponse struct { + StatusCode int +} + +func (response TextExampledefaultResponse) VisitTextExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type UnknownExampleRequestObject struct { + Body io.Reader +} + +type UnknownExampleResponseObject interface { + VisitUnknownExampleResponse(w http.ResponseWriter) error +} + +type UnknownExample200Videomp4Response struct { + Body io.Reader + ContentLength int64 +} + +func (response UnknownExample200Videomp4Response) VisitUnknownExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "video/mp4") + if response.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + w.WriteHeader(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(w, response.Body) + return err +} + +type UnknownExample400Response = BadrequestResponse + +func (response UnknownExample400Response) VisitUnknownExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type UnknownExampledefaultResponse struct { + StatusCode int +} + +func (response UnknownExampledefaultResponse) VisitUnknownExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type UnspecifiedContentTypeRequestObject struct { + ContentType string + Body io.Reader +} + +type UnspecifiedContentTypeResponseObject interface { + VisitUnspecifiedContentTypeResponse(w http.ResponseWriter) error +} + +type UnspecifiedContentType200VideoResponse struct { + Body io.Reader + ContentType string + ContentLength int64 +} + +func (response UnspecifiedContentType200VideoResponse) VisitUnspecifiedContentTypeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", response.ContentType) + if response.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + w.WriteHeader(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(w, response.Body) + return err +} + +type UnspecifiedContentType400Response = BadrequestResponse + +func (response UnspecifiedContentType400Response) VisitUnspecifiedContentTypeResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type UnspecifiedContentType401Response struct { +} + +func (response UnspecifiedContentType401Response) VisitUnspecifiedContentTypeResponse(w http.ResponseWriter) error { + w.WriteHeader(401) + return nil +} + +type UnspecifiedContentType403Response struct { +} + +func (response UnspecifiedContentType403Response) VisitUnspecifiedContentTypeResponse(w http.ResponseWriter) error { + w.WriteHeader(403) + return nil +} + +type UnspecifiedContentTypedefaultResponse struct { + StatusCode int +} + +func (response UnspecifiedContentTypedefaultResponse) VisitUnspecifiedContentTypeResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type URLEncodedExampleRequestObject struct { + Body *URLEncodedExampleFormdataRequestBody +} + +type URLEncodedExampleResponseObject interface { + VisitURLEncodedExampleResponse(w http.ResponseWriter) error +} + +type URLEncodedExample200FormdataResponse Example + +func (response URLEncodedExample200FormdataResponse) VisitURLEncodedExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/x-www-form-urlencoded") + w.WriteHeader(200) + + if form, err := runtime.MarshalForm(response, nil); err != nil { + return err + } else { + _, err := w.Write([]byte(form.Encode())) + return err + } +} + +type URLEncodedExample400Response = BadrequestResponse + +func (response URLEncodedExample400Response) VisitURLEncodedExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type URLEncodedExampledefaultResponse struct { + StatusCode int +} + +func (response URLEncodedExampledefaultResponse) VisitURLEncodedExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type HeadersExampleRequestObject struct { + Params HeadersExampleParams + Body *HeadersExampleJSONRequestBody +} + +type HeadersExampleResponseObject interface { + VisitHeadersExampleResponse(w http.ResponseWriter) error +} + +type HeadersExample200ResponseHeaders struct { + Header1 string + Header2 int +} + +type HeadersExample200JSONResponse struct { + Body Example + Headers HeadersExample200ResponseHeaders +} + +func (response HeadersExample200JSONResponse) VisitHeadersExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) + w.Header().Set("header2", fmt.Sprint(response.Headers.Header2)) + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response.Body) +} + +type HeadersExample400Response = BadrequestResponse + +func (response HeadersExample400Response) VisitHeadersExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type HeadersExampledefaultResponse struct { + StatusCode int +} + +func (response HeadersExampledefaultResponse) VisitHeadersExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +type UnionExampleRequestObject struct { + Body *UnionExampleJSONRequestBody +} + +type UnionExampleResponseObject interface { + VisitUnionExampleResponse(w http.ResponseWriter) error +} + +type UnionExample200ResponseHeaders struct { + Header1 string + Header2 int +} + +type UnionExample200ApplicationAlternativePlusJSONResponse struct { + Body Example + Headers UnionExample200ResponseHeaders +} + +func (response UnionExample200ApplicationAlternativePlusJSONResponse) VisitUnionExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/alternative+json") + w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) + w.Header().Set("header2", fmt.Sprint(response.Headers.Header2)) + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response.Body) +} + +type UnionExample200JSONResponse struct { + Body struct { + union json.RawMessage + } + Headers UnionExample200ResponseHeaders +} + +func (response UnionExample200JSONResponse) VisitUnionExampleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) + w.Header().Set("header2", fmt.Sprint(response.Headers.Header2)) + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response.Body.union) +} + +type UnionExample400Response = BadrequestResponse + +func (response UnionExample400Response) VisitUnionExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type UnionExampledefaultResponse struct { + StatusCode int +} + +func (response UnionExampledefaultResponse) VisitUnionExampleResponse(w http.ResponseWriter) error { + w.WriteHeader(response.StatusCode) + return nil +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (POST /json) + JSONExample(ctx context.Context, request JSONExampleRequestObject) (JSONExampleResponseObject, error) + + // (POST /multipart) + MultipartExample(ctx context.Context, request MultipartExampleRequestObject) (MultipartExampleResponseObject, error) + + // (POST /multipart-related) + MultipartRelatedExample(ctx context.Context, request MultipartRelatedExampleRequestObject) (MultipartRelatedExampleResponseObject, error) + + // (POST /multiple) + MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) + + // (POST /required-json-body) + RequiredJSONBody(ctx context.Context, request RequiredJSONBodyRequestObject) (RequiredJSONBodyResponseObject, error) + + // (POST /required-text-body) + RequiredTextBody(ctx context.Context, request RequiredTextBodyRequestObject) (RequiredTextBodyResponseObject, error) + + // (GET /reserved-go-keyword-parameters/{type}) + ReservedGoKeywordParameters(ctx context.Context, request ReservedGoKeywordParametersRequestObject) (ReservedGoKeywordParametersResponseObject, error) + + // (POST /reusable-responses) + ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) + + // (POST /text) + TextExample(ctx context.Context, request TextExampleRequestObject) (TextExampleResponseObject, error) + + // (POST /unknown) + UnknownExample(ctx context.Context, request UnknownExampleRequestObject) (UnknownExampleResponseObject, error) + + // (POST /unspecified-content-type) + UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) (UnspecifiedContentTypeResponseObject, error) + + // (POST /urlencoded) + URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) (URLEncodedExampleResponseObject, error) + + // (POST /with-headers) + HeadersExample(ctx context.Context, request HeadersExampleRequestObject) (HeadersExampleResponseObject, error) + + // (POST /with-union) + UnionExample(ctx context.Context, request UnionExampleRequestObject) (UnionExampleResponseObject, error) +} + +type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc +type StrictMiddlewareFunc = strictnethttp.StrictHTTPMiddlewareFunc + +type StrictHTTPServerOptions struct { + RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictHTTPServerOptions{ + RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + }, + ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusInternalServerError) + }, + }} +} + +func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictHTTPServerOptions) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: options} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc + options StrictHTTPServerOptions +} + +// JSONExample operation middleware +func (sh *strictHandler) JSONExample(w http.ResponseWriter, r *http.Request) { + var request JSONExampleRequestObject + + var body JSONExampleJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + if !errors.Is(err, io.EOF) { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + } else { + request.Body = &body + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.JSONExample(ctx, request.(JSONExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "JSONExample") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(JSONExampleResponseObject); ok { + if err := validResponse.VisitJSONExampleResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// MultipartExample operation middleware +func (sh *strictHandler) MultipartExample(w http.ResponseWriter, r *http.Request) { + var request MultipartExampleRequestObject + + if reader, err := r.MultipartReader(); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode multipart body: %w", err)) + return + } else { + request.Body = reader + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.MultipartExample(ctx, request.(MultipartExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipartExample") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(MultipartExampleResponseObject); ok { + if err := validResponse.VisitMultipartExampleResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// MultipartRelatedExample operation middleware +func (sh *strictHandler) MultipartRelatedExample(w http.ResponseWriter, r *http.Request) { + var request MultipartRelatedExampleRequestObject + + if _, params, err := mime.ParseMediaType(r.Header.Get("Content-Type")); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, err) + return + } else if boundary := params["boundary"]; boundary == "" { + sh.options.RequestErrorHandlerFunc(w, r, http.ErrMissingBoundary) + return + } else { + request.Body = multipart.NewReader(r.Body, boundary) + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.MultipartRelatedExample(ctx, request.(MultipartRelatedExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipartRelatedExample") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(MultipartRelatedExampleResponseObject); ok { + if err := validResponse.VisitMultipartRelatedExampleResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// MultipleRequestAndResponseTypes operation middleware +func (sh *strictHandler) MultipleRequestAndResponseTypes(w http.ResponseWriter, r *http.Request) { + var request MultipleRequestAndResponseTypesRequestObject + + if strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") { + + var body MultipleRequestAndResponseTypesJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + if !errors.Is(err, io.EOF) { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + } else { + request.JSONBody = &body + } + } + if strings.HasPrefix(r.Header.Get("Content-Type"), "application/x-www-form-urlencoded") { + if err := r.ParseForm(); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode formdata: %w", err)) + return + } + var body MultipleRequestAndResponseTypesFormdataRequestBody + if err := runtime.BindForm(&body, r.Form, nil, nil); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't bind formdata: %w", err)) + return + } + request.FormdataBody = &body + } + if strings.HasPrefix(r.Header.Get("Content-Type"), "image/png") { + request.Body = r.Body + } + if strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/form-data") { + if reader, err := r.MultipartReader(); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode multipart body: %w", err)) + return + } else { + request.MultipartBody = reader + } + } + if strings.HasPrefix(r.Header.Get("Content-Type"), "text/plain") { + data, err := io.ReadAll(r.Body) + if err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't read body: %w", err)) + return + } + if len(data) > 0 { + body := MultipleRequestAndResponseTypesTextRequestBody(data) + request.TextBody = &body + } + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.MultipleRequestAndResponseTypes(ctx, request.(MultipleRequestAndResponseTypesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipleRequestAndResponseTypes") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(MultipleRequestAndResponseTypesResponseObject); ok { + if err := validResponse.VisitMultipleRequestAndResponseTypesResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// RequiredJSONBody operation middleware +func (sh *strictHandler) RequiredJSONBody(w http.ResponseWriter, r *http.Request) { + var request RequiredJSONBodyRequestObject + + var body RequiredJSONBodyJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.RequiredJSONBody(ctx, request.(RequiredJSONBodyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "RequiredJSONBody") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(RequiredJSONBodyResponseObject); ok { + if err := validResponse.VisitRequiredJSONBodyResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// RequiredTextBody operation middleware +func (sh *strictHandler) RequiredTextBody(w http.ResponseWriter, r *http.Request) { + var request RequiredTextBodyRequestObject + + data, err := io.ReadAll(r.Body) + if err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't read body: %w", err)) + return + } + body := RequiredTextBodyTextRequestBody(data) + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.RequiredTextBody(ctx, request.(RequiredTextBodyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "RequiredTextBody") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(RequiredTextBodyResponseObject); ok { + if err := validResponse.VisitRequiredTextBodyResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// ReservedGoKeywordParameters operation middleware +func (sh *strictHandler) ReservedGoKeywordParameters(w http.ResponseWriter, r *http.Request, pType string) { + var request ReservedGoKeywordParametersRequestObject + + request.Type = pType + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.ReservedGoKeywordParameters(ctx, request.(ReservedGoKeywordParametersRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReservedGoKeywordParameters") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(ReservedGoKeywordParametersResponseObject); ok { + if err := validResponse.VisitReservedGoKeywordParametersResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// ReusableResponses operation middleware +func (sh *strictHandler) ReusableResponses(w http.ResponseWriter, r *http.Request) { + var request ReusableResponsesRequestObject + + var body ReusableResponsesJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + if !errors.Is(err, io.EOF) { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + } else { + request.Body = &body + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.ReusableResponses(ctx, request.(ReusableResponsesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReusableResponses") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(ReusableResponsesResponseObject); ok { + if err := validResponse.VisitReusableResponsesResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// TextExample operation middleware +func (sh *strictHandler) TextExample(w http.ResponseWriter, r *http.Request) { + var request TextExampleRequestObject + + data, err := io.ReadAll(r.Body) + if err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't read body: %w", err)) + return + } + if len(data) > 0 { + body := TextExampleTextRequestBody(data) + request.Body = &body + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.TextExample(ctx, request.(TextExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "TextExample") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(TextExampleResponseObject); ok { + if err := validResponse.VisitTextExampleResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// UnknownExample operation middleware +func (sh *strictHandler) UnknownExample(w http.ResponseWriter, r *http.Request) { + var request UnknownExampleRequestObject + + request.Body = r.Body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.UnknownExample(ctx, request.(UnknownExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnknownExample") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(UnknownExampleResponseObject); ok { + if err := validResponse.VisitUnknownExampleResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// UnspecifiedContentType operation middleware +func (sh *strictHandler) UnspecifiedContentType(w http.ResponseWriter, r *http.Request) { + var request UnspecifiedContentTypeRequestObject + + request.ContentType = r.Header.Get("Content-Type") + + request.Body = r.Body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.UnspecifiedContentType(ctx, request.(UnspecifiedContentTypeRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnspecifiedContentType") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(UnspecifiedContentTypeResponseObject); ok { + if err := validResponse.VisitUnspecifiedContentTypeResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// URLEncodedExample operation middleware +func (sh *strictHandler) URLEncodedExample(w http.ResponseWriter, r *http.Request) { + var request URLEncodedExampleRequestObject + + if err := r.ParseForm(); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode formdata: %w", err)) + return + } + var body URLEncodedExampleFormdataRequestBody + if err := runtime.BindForm(&body, r.Form, nil, nil); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't bind formdata: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.URLEncodedExample(ctx, request.(URLEncodedExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "URLEncodedExample") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(URLEncodedExampleResponseObject); ok { + if err := validResponse.VisitURLEncodedExampleResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// HeadersExample operation middleware +func (sh *strictHandler) HeadersExample(w http.ResponseWriter, r *http.Request, params HeadersExampleParams) { + var request HeadersExampleRequestObject + + request.Params = params + + var body HeadersExampleJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + if !errors.Is(err, io.EOF) { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + } else { + request.Body = &body + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.HeadersExample(ctx, request.(HeadersExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "HeadersExample") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(HeadersExampleResponseObject); ok { + if err := validResponse.VisitHeadersExampleResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// UnionExample operation middleware +func (sh *strictHandler) UnionExample(w http.ResponseWriter, r *http.Request) { + var request UnionExampleRequestObject + + var body UnionExampleJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + if !errors.Is(err, io.EOF) { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + } else { + request.Body = &body + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.UnionExample(ctx, request.(UnionExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnionExample") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(UnionExampleResponseObject); ok { + if err := validResponse.VisitUnionExampleResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/+xZTXPbNhP+Kzt431NKmrbjE2+NJ5O2aeuObJ86PkDESkJCAiiwFK3R6L93QEBfFq1K", + "qT4ynt5Ecr/wPLuLBTRlha6MVqjIsXzKLDqjlcP2oc+Fxb9qdOSfBLrCSkNSK5azD1z04rdZwizWjvdL", + "nKt7+UIrQtWqcmNKWXCvmn1xXn/KXDHCivtf/7c4YDn7X7YMJQtfXYbPvDIlstlslryI4O4zS9gIuUDb", + "Rht+Xq3bpolBljNHVqoh80aC2HWnmFSEQ7TemxeNQXiBeRz5lBmrDVqSAaMxL2vs9hTf6P4XLCisQKqB", + "3sTyViviUjkQcjBAi4ogggfehgNXG6MtoYD+BLyHgsChHaNlCSNJPjB2v/oeYsCOJWyM1gVHVxeXF5ee", + "L21QcSNZzt63rxJmOI3aBS0IMrqL91/u734H6YDXpCtOsuBlOYGKWzfiJQqQirQPsS7IXbDWk22J/1lE", + "7Y8RSp81bQJ90GJyjIRp83Ilna8vL0+Ul7OE3QRnXTYWQWUrBdaaGfC67MD8UX1VulGA1mobV5ZVdUnS", + "cEurXK2j/dtcZBfIF/aygbZVKjjxI6F+KE/nBj61WHJCsQMBvSC5Hw8r5o/Kwr/xc1YOYj/u7FP3I904", + "GOkGSINAXkIjaQRzxRcNVirg4KQalgjzoJJOMkuM296PSvTiWh68jaP3s2TNynPaNE3aFlBtS1SFFt9G", + "YcJkxYeYGTVcV/e2ObGc9SfkU3ZzgztQISeM8JkyU3Kptu/eJ2rp/yF9sMIO5eofpUWRekbSfqyP7sKN", + "5QVeyg8ac90EnIZKOl+l4aMb6boUwMuGT1zoD5sTRy+q+8mjLcyjjx3BgffJcrI1vu0xZEGtz6zzUPuA", + "z/SP1O6R+PvSd+qa2p+i9kwg0qFOv+Kk0VakhlteIaF12dTHOfO2hthh8o+FJBRcQR9B8QoF8AGhhU8a", + "oknXwU/w+0l/DiJLU+2BY/GQ/zllHrz2EMIS5h2wPOD3ko5kC8BPx6VqjmY46qZrrl5L+Cgyh87iwPmB", + "pIvjDvyCp96KxHmOTNtzc+Pwf4qk9ky+Pnj7lrDLsH3AweN77wJ1ePk6ZlFrF9i+cY7ZAcWxFKizytzs", + "aflsoDqDhRxIFGlcRRpie60l3GpVWKT1A4jfDJUmWBiD/gRohBAQaPfHBqGqHYHhzoGktouUMtwVCdxo", + "Ho/LyG6Dp4dlO93G6rsjcfruXIzeXF7tr/L+yHmzdpB4pR57v34MMvvemB3sxLLneetwfs9Uzo2kUbpy", + "pdxdwj8FgeWeXqAc+4lICbBItVUoYCz5/Bp0ozajgSWtXbNQCGM5Dc2vt/cZiJKttq5Zsu0K/OkNX9Ae", + "84+DU+VpreS2i/pH/xniDP1yb5BafafX8LwktIqTHOMPh7m/2bSiFd4N2kp7wXKyo4ent5dVs4SFf45C", + "C6pt6fsEkcmzLPzjdOEaPhyivZA640Z6FP4OAAD//0nTejA+HAAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/strict-server/stdhttp/server.go b/internal/test/strict-server/stdhttp/server.go new file mode 100644 index 0000000000..4f39f939cc --- /dev/null +++ b/internal/test/strict-server/stdhttp/server.go @@ -0,0 +1,154 @@ +//go:build go1.22 + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=server.cfg.yaml ../strict-schema.yaml +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=types.cfg.yaml ../strict-schema.yaml + +package api + +import ( + "context" + "encoding/json" + "io" + "mime/multipart" +) + +type StrictServer struct { +} + +func (s StrictServer) JSONExample(ctx context.Context, request JSONExampleRequestObject) (JSONExampleResponseObject, error) { + return JSONExample200JSONResponse(*request.Body), nil +} + +func (s StrictServer) MultipartExample(ctx context.Context, request MultipartExampleRequestObject) (MultipartExampleResponseObject, error) { + return MultipartExample200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.Body.NextPart() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + w, err := writer.CreatePart(part.Header) + if err != nil { + return err + } + _, err = io.Copy(w, part) + if err != nil { + return err + } + if err = part.Close(); err != nil { + return err + } + } + }), nil +} + +func (s StrictServer) MultipartRelatedExample(ctx context.Context, request MultipartRelatedExampleRequestObject) (MultipartRelatedExampleResponseObject, error) { + return MultipartRelatedExample200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.Body.NextPart() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + w, err := writer.CreatePart(part.Header) + if err != nil { + return err + } + _, err = io.Copy(w, part) + if err != nil { + return err + } + if err = part.Close(); err != nil { + return err + } + } + }), nil +} + +func (s StrictServer) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) { + switch { + case request.Body != nil: + return MultipleRequestAndResponseTypes200ImagepngResponse{Body: request.Body}, nil + case request.JSONBody != nil: + return MultipleRequestAndResponseTypes200JSONResponse(*request.JSONBody), nil + case request.FormdataBody != nil: + return MultipleRequestAndResponseTypes200FormdataResponse(*request.FormdataBody), nil + case request.TextBody != nil: + return MultipleRequestAndResponseTypes200TextResponse(*request.TextBody), nil + case request.MultipartBody != nil: + return MultipleRequestAndResponseTypes200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.MultipartBody.NextPart() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + w, err := writer.CreatePart(part.Header) + if err != nil { + return err + } + _, err = io.Copy(w, part) + if err != nil { + return err + } + if err = part.Close(); err != nil { + return err + } + } + }), nil + default: + return MultipleRequestAndResponseTypes400Response{}, nil + } +} + +func (s StrictServer) TextExample(ctx context.Context, request TextExampleRequestObject) (TextExampleResponseObject, error) { + return TextExample200TextResponse(*request.Body), nil +} + +func (s StrictServer) UnknownExample(ctx context.Context, request UnknownExampleRequestObject) (UnknownExampleResponseObject, error) { + return UnknownExample200Videomp4Response{Body: request.Body}, nil +} + +func (s StrictServer) UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) (UnspecifiedContentTypeResponseObject, error) { + return UnspecifiedContentType200VideoResponse{Body: request.Body, ContentType: request.ContentType}, nil +} + +func (s StrictServer) URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) (URLEncodedExampleResponseObject, error) { + return URLEncodedExample200FormdataResponse(*request.Body), nil +} + +func (s StrictServer) HeadersExample(ctx context.Context, request HeadersExampleRequestObject) (HeadersExampleResponseObject, error) { + return HeadersExample200JSONResponse{Body: *request.Body, Headers: HeadersExample200ResponseHeaders{Header1: request.Params.Header1, Header2: *request.Params.Header2}}, nil +} + +func (s StrictServer) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) { + return ReusableResponses200JSONResponse{ReusableresponseJSONResponse: ReusableresponseJSONResponse{Body: *request.Body}}, nil +} + +func (s StrictServer) RequiredJSONBody(ctx context.Context, request RequiredJSONBodyRequestObject) (RequiredJSONBodyResponseObject, error) { + return RequiredJSONBody200JSONResponse(*request.Body), nil +} + +func (s StrictServer) RequiredTextBody(ctx context.Context, request RequiredTextBodyRequestObject) (RequiredTextBodyResponseObject, error) { + return RequiredTextBody200TextResponse(*request.Body), nil +} + +func (s StrictServer) ReservedGoKeywordParameters(ctx context.Context, request ReservedGoKeywordParametersRequestObject) (ReservedGoKeywordParametersResponseObject, error) { + return ReservedGoKeywordParameters200TextResponse(""), nil +} + +func (s StrictServer) UnionExample(ctx context.Context, request UnionExampleRequestObject) (UnionExampleResponseObject, error) { + union, err := json.Marshal(*request.Body) + if err != nil { + return nil, err + } + + return UnionExample200JSONResponse{ + Body: struct{ union json.RawMessage }{ + union: union, + }, + }, nil +} diff --git a/internal/test/strict-server/stdhttp/std_strict_test.go b/internal/test/strict-server/stdhttp/std_strict_test.go new file mode 100644 index 0000000000..40d362a54a --- /dev/null +++ b/internal/test/strict-server/stdhttp/std_strict_test.go @@ -0,0 +1,229 @@ +//go:build go1.22 + +package api + +import ( + "bytes" + "encoding/json" + "io" + "mime" + "mime/multipart" + "net/http" + "net/url" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + clientAPI "github.com/oapi-codegen/oapi-codegen/v2/internal/test/strict-server/client" + "github.com/oapi-codegen/runtime" + "github.com/oapi-codegen/testutil" +) + +func TestStdHTTPServer(t *testing.T) { + server := StrictServer{} + strictHandler := NewStrictHandler(server, nil) + m := http.NewServeMux() + HandlerFromMux(strictHandler, m) + testImpl(t, m) +} + +func testImpl(t *testing.T, handler http.Handler) { + t.Run("JSONExample", func(t *testing.T) { + value := "123" + requestBody := clientAPI.Example{Value: &value} + rr := testutil.NewRequest().Post("/json").WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) + var responseBody clientAPI.Example + err := json.NewDecoder(rr.Body).Decode(&responseBody) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + }) + t.Run("URLEncodedExample", func(t *testing.T) { + value := "456" + requestBody := clientAPI.Example{Value: &value} + requestBodyEncoded, err := runtime.MarshalForm(&requestBody, nil) + assert.NoError(t, err) + rr := testutil.NewRequest().Post("/urlencoded").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(requestBodyEncoded.Encode())).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "application/x-www-form-urlencoded", rr.Header().Get("Content-Type")) + values, err := url.ParseQuery(rr.Body.String()) + assert.NoError(t, err) + var responseBody clientAPI.Example + err = runtime.BindForm(&responseBody, values, nil, nil) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + }) + t.Run("MultipartExample", func(t *testing.T) { + value := "789" + fieldName := "value" + var writer bytes.Buffer + mw := multipart.NewWriter(&writer) + field, err := mw.CreateFormField(fieldName) + assert.NoError(t, err) + _, _ = field.Write([]byte(value)) + assert.NoError(t, mw.Close()) + rr := testutil.NewRequest().Post("/multipart").WithContentType(mw.FormDataContentType()).WithBody(writer.Bytes()).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + contentType, params, err := mime.ParseMediaType(rr.Header().Get("Content-Type")) + assert.NoError(t, err) + assert.Equal(t, "multipart/form-data", contentType) + reader := multipart.NewReader(rr.Body, params["boundary"]) + part, err := reader.NextPart() + assert.NoError(t, err) + assert.Equal(t, part.FormName(), fieldName) + readValue, err := io.ReadAll(part) + assert.NoError(t, err) + assert.Equal(t, value, string(readValue)) + _, err = reader.NextPart() + assert.Equal(t, io.EOF, err) + }) + t.Run("MultipartRelatedExample", func(t *testing.T) { + value := "789" + fieldName := "value" + var writer bytes.Buffer + mw := multipart.NewWriter(&writer) + field, err := mw.CreateFormField(fieldName) + assert.NoError(t, err) + _, _ = field.Write([]byte(value)) + assert.NoError(t, mw.Close()) + rr := testutil.NewRequest().Post("/multipart-related").WithContentType(mime.FormatMediaType("multipart/related", map[string]string{"boundary": mw.Boundary()})).WithBody(writer.Bytes()).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + contentType, params, err := mime.ParseMediaType(rr.Header().Get("Content-Type")) + assert.NoError(t, err) + assert.Equal(t, "multipart/related", contentType) + reader := multipart.NewReader(rr.Body, params["boundary"]) + part, err := reader.NextPart() + assert.NoError(t, err) + assert.Equal(t, part.FormName(), fieldName) + readValue, err := io.ReadAll(part) + assert.NoError(t, err) + assert.Equal(t, value, string(readValue)) + _, err = reader.NextPart() + assert.Equal(t, io.EOF, err) + }) + t.Run("TextExample", func(t *testing.T) { + value := "text" + rr := testutil.NewRequest().Post("/text").WithContentType("text/plain").WithBody([]byte(value)).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "text/plain", rr.Header().Get("Content-Type")) + assert.Equal(t, value, rr.Body.String()) + }) + t.Run("UnknownExample", func(t *testing.T) { + data := []byte("unknown data") + rr := testutil.NewRequest().Post("/unknown").WithContentType("image/png").WithBody(data).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "video/mp4", rr.Header().Get("Content-Type")) + assert.Equal(t, data, rr.Body.Bytes()) + }) + t.Run("MultipleRequestAndResponseTypesJSON", func(t *testing.T) { + value := "123" + requestBody := clientAPI.Example{Value: &value} + rr := testutil.NewRequest().Post("/multiple").WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) + var responseBody clientAPI.Example + err := json.NewDecoder(rr.Body).Decode(&responseBody) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + }) + t.Run("MultipleRequestAndResponseTypesFormdata", func(t *testing.T) { + value := "456" + requestBody := clientAPI.Example{Value: &value} + requestBodyEncoded, err := runtime.MarshalForm(&requestBody, nil) + assert.NoError(t, err) + rr := testutil.NewRequest().Post("/multiple").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(requestBodyEncoded.Encode())).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "application/x-www-form-urlencoded", rr.Header().Get("Content-Type")) + values, err := url.ParseQuery(rr.Body.String()) + assert.NoError(t, err) + var responseBody clientAPI.Example + err = runtime.BindForm(&responseBody, values, nil, nil) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + }) + t.Run("MultipleRequestAndResponseTypesMultipart", func(t *testing.T) { + value := "789" + fieldName := "value" + var writer bytes.Buffer + mw := multipart.NewWriter(&writer) + field, err := mw.CreateFormField(fieldName) + assert.NoError(t, err) + _, _ = field.Write([]byte(value)) + assert.NoError(t, mw.Close()) + rr := testutil.NewRequest().Post("/multiple").WithContentType(mw.FormDataContentType()).WithBody(writer.Bytes()).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + contentType, params, err := mime.ParseMediaType(rr.Header().Get("Content-Type")) + assert.NoError(t, err) + assert.Equal(t, "multipart/form-data", contentType) + reader := multipart.NewReader(rr.Body, params["boundary"]) + part, err := reader.NextPart() + assert.NoError(t, err) + assert.Equal(t, part.FormName(), fieldName) + readValue, err := io.ReadAll(part) + assert.NoError(t, err) + assert.Equal(t, value, string(readValue)) + _, err = reader.NextPart() + assert.Equal(t, io.EOF, err) + }) + t.Run("MultipleRequestAndResponseTypesText", func(t *testing.T) { + value := "text" + rr := testutil.NewRequest().Post("/multiple").WithContentType("text/plain").WithBody([]byte(value)).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "text/plain", rr.Header().Get("Content-Type")) + assert.Equal(t, value, rr.Body.String()) + }) + t.Run("MultipleRequestAndResponseTypesImage", func(t *testing.T) { + data := []byte("unknown data") + rr := testutil.NewRequest().Post("/multiple").WithContentType("image/png").WithBody(data).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "image/png", rr.Header().Get("Content-Type")) + assert.Equal(t, data, rr.Body.Bytes()) + }) + t.Run("HeadersExample", func(t *testing.T) { + header1 := "value1" + header2 := "890" + value := "asdf" + requestBody := clientAPI.Example{Value: &value} + rr := testutil.NewRequest().Post("/with-headers").WithHeader("header1", header1).WithHeader("header2", header2).WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) + var responseBody clientAPI.Example + err := json.NewDecoder(rr.Body).Decode(&responseBody) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + assert.Equal(t, header1, rr.Header().Get("header1")) + assert.Equal(t, header2, rr.Header().Get("header2")) + }) + t.Run("UnspecifiedContentType", func(t *testing.T) { + data := []byte("image data") + contentType := "image/jpeg" + rr := testutil.NewRequest().Post("/unspecified-content-type").WithContentType(contentType).WithBody(data).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, contentType, rr.Header().Get("Content-Type")) + assert.Equal(t, data, rr.Body.Bytes()) + }) + t.Run("ReusableResponses", func(t *testing.T) { + value := "jkl;" + requestBody := clientAPI.Example{Value: &value} + rr := testutil.NewRequest().Post("/reusable-responses").WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) + var responseBody clientAPI.Example + err := json.NewDecoder(rr.Body).Decode(&responseBody) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + }) + t.Run("UnionResponses", func(t *testing.T) { + value := "union" + requestBody := clientAPI.Example{Value: &value} + rr := testutil.NewRequest().Post("/with-union").WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) + var responseBody clientAPI.Example + err := json.NewDecoder(rr.Body).Decode(&responseBody) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + }) +} diff --git a/internal/test/strict-server/stdhttp/tools/tools.go b/internal/test/strict-server/stdhttp/tools/tools.go new file mode 100644 index 0000000000..8615cb4c57 --- /dev/null +++ b/internal/test/strict-server/stdhttp/tools/tools.go @@ -0,0 +1,8 @@ +//go:build tools +// +build tools + +package tools + +import ( + _ "github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen" +) diff --git a/internal/test/strict-server/stdhttp/types.cfg.yaml b/internal/test/strict-server/stdhttp/types.cfg.yaml new file mode 100644 index 0000000000..2ed9740ea7 --- /dev/null +++ b/internal/test/strict-server/stdhttp/types.cfg.yaml @@ -0,0 +1,5 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +generate: + models: true +output: types.gen.go diff --git a/internal/test/strict-server/stdhttp/types.gen.go b/internal/test/strict-server/stdhttp/types.gen.go new file mode 100644 index 0000000000..fac014c4bb --- /dev/null +++ b/internal/test/strict-server/stdhttp/types.gen.go @@ -0,0 +1,69 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package api + +// Example defines model for example. +type Example struct { + Value *string `json:"value,omitempty"` +} + +// Reusableresponse defines model for reusableresponse. +type Reusableresponse = Example + +// MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesTextBody = string + +// RequiredTextBodyTextBody defines parameters for RequiredTextBody. +type RequiredTextBodyTextBody = string + +// TextExampleTextBody defines parameters for TextExample. +type TextExampleTextBody = string + +// HeadersExampleParams defines parameters for HeadersExample. +type HeadersExampleParams struct { + Header1 string `json:"header1"` + Header2 *int `json:"header2,omitempty"` +} + +// JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. +type JSONExampleJSONRequestBody = Example + +// MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. +type MultipartExampleMultipartRequestBody = Example + +// MultipartRelatedExampleMultipartRequestBody defines body for MultipartRelatedExample for multipart/related ContentType. +type MultipartRelatedExampleMultipartRequestBody = Example + +// MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. +type MultipleRequestAndResponseTypesJSONRequestBody = Example + +// MultipleRequestAndResponseTypesFormdataRequestBody defines body for MultipleRequestAndResponseTypes for application/x-www-form-urlencoded ContentType. +type MultipleRequestAndResponseTypesFormdataRequestBody = Example + +// MultipleRequestAndResponseTypesMultipartRequestBody defines body for MultipleRequestAndResponseTypes for multipart/form-data ContentType. +type MultipleRequestAndResponseTypesMultipartRequestBody = Example + +// MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. +type MultipleRequestAndResponseTypesTextRequestBody = MultipleRequestAndResponseTypesTextBody + +// RequiredJSONBodyJSONRequestBody defines body for RequiredJSONBody for application/json ContentType. +type RequiredJSONBodyJSONRequestBody = Example + +// RequiredTextBodyTextRequestBody defines body for RequiredTextBody for text/plain ContentType. +type RequiredTextBodyTextRequestBody = RequiredTextBodyTextBody + +// ReusableResponsesJSONRequestBody defines body for ReusableResponses for application/json ContentType. +type ReusableResponsesJSONRequestBody = Example + +// TextExampleTextRequestBody defines body for TextExample for text/plain ContentType. +type TextExampleTextRequestBody = TextExampleTextBody + +// URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. +type URLEncodedExampleFormdataRequestBody = Example + +// HeadersExampleJSONRequestBody defines body for HeadersExample for application/json ContentType. +type HeadersExampleJSONRequestBody = Example + +// UnionExampleJSONRequestBody defines body for UnionExample for application/json ContentType. +type UnionExampleJSONRequestBody = Example diff --git a/internal/test/strict-server/strict-schema.yaml b/internal/test/strict-server/strict-schema.yaml index a9a2c41097..8b18edd541 100644 --- a/internal/test/strict-server/strict-schema.yaml +++ b/internal/test/strict-server/strict-schema.yaml @@ -9,7 +9,7 @@ paths: /json: post: operationId: JSONExample - description: JSON is automatically marshalled into structs. + description: JSON is automatically marshaled into structs. requestBody: content: application/json: @@ -64,6 +64,25 @@ paths: $ref: "#/components/responses/badrequest" default: description: Unknown error + /multipart-related: + post: + operationId: MultipartRelatedExample + requestBody: + content: + multipart/related: + schema: + $ref: "#/components/schemas/example" + responses: + 200: + description: OK + content: + multipart/related: + schema: + $ref: "#/components/schemas/example" + 400: + $ref: "#/components/responses/badrequest" + default: + description: Unknown error /text: post: operationId: TextExample @@ -228,6 +247,97 @@ paths: $ref: "#/components/responses/badrequest" default: description: Unknown error + /required-json-body: + post: + operationId: RequiredJSONBody + description: Request body is required, so missing body should always error. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/example" + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/example" + 400: + $ref: "#/components/responses/badrequest" + default: + description: Unknown error + /required-text-body: + post: + operationId: RequiredTextBody + description: Request body is required, so missing body should always error. + requestBody: + required: true + content: + text/plain: + schema: + type: string + responses: + 200: + description: OK + content: + text/plain: + schema: + type: string + 400: + $ref: "#/components/responses/badrequest" + default: + description: Unknown error + /reserved-go-keyword-parameters/{type}: + get: + operationId: ReservedGoKeywordParameters + description: Parameters can be named after Go keywords + parameters: + - name: type + in: path + required: true + schema: + type: string + responses: + 200: + description: OK + content: + text/plain: + schema: + type: string + /with-union: + post: + operationId: UnionExample + description: Union type + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/example" + responses: + 200: + description: OK + headers: + header1: + schema: + type: string + header2: + schema: + type: integer + content: + application/json: + schema: + oneOf: + - type: string + - $ref: "#/components/schemas/example" + application/alternative+json: + schema: + $ref: "#/components/schemas/example" + 400: + $ref: "#/components/responses/badrequest" + default: + description: Unknown error components: responses: badrequest: diff --git a/internal/test/strict-server/strict_test.go b/internal/test/strict-server/strict_test.go index 860cc4cb8d..03cade9cc9 100644 --- a/internal/test/strict-server/strict_test.go +++ b/internal/test/strict-server/strict_test.go @@ -13,57 +13,68 @@ import ( "github.com/gin-gonic/gin" "github.com/go-chi/chi/v5" + "github.com/kataras/iris/v12" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" - "github.com/deepmap/oapi-codegen/internal/test/strict-server/chi" - api3 "github.com/deepmap/oapi-codegen/internal/test/strict-server/client" - api4 "github.com/deepmap/oapi-codegen/internal/test/strict-server/echo" - api2 "github.com/deepmap/oapi-codegen/internal/test/strict-server/gin" - "github.com/deepmap/oapi-codegen/pkg/runtime" - "github.com/deepmap/oapi-codegen/pkg/testutil" + chiAPI "github.com/oapi-codegen/oapi-codegen/v2/internal/test/strict-server/chi" + clientAPI "github.com/oapi-codegen/oapi-codegen/v2/internal/test/strict-server/client" + echoAPI "github.com/oapi-codegen/oapi-codegen/v2/internal/test/strict-server/echo" + ginAPI "github.com/oapi-codegen/oapi-codegen/v2/internal/test/strict-server/gin" + irisAPI "github.com/oapi-codegen/oapi-codegen/v2/internal/test/strict-server/iris" + + "github.com/oapi-codegen/runtime" + "github.com/oapi-codegen/testutil" ) +func TestIrisServer(t *testing.T) { + server := irisAPI.StrictServer{} + strictHandler := irisAPI.NewStrictHandler(server, nil) + i := iris.New() + irisAPI.RegisterHandlers(i, strictHandler) + testImpl(t, i) +} + func TestChiServer(t *testing.T) { - server := api.StrictServer{} - strictHandler := api.NewStrictHandler(server, nil) + server := chiAPI.StrictServer{} + strictHandler := chiAPI.NewStrictHandler(server, nil) r := chi.NewRouter() - handler := api.HandlerFromMux(strictHandler, r) + handler := chiAPI.HandlerFromMux(strictHandler, r) testImpl(t, handler) } func TestEchoServer(t *testing.T) { - server := api4.StrictServer{} - strictHandler := api4.NewStrictHandler(server, nil) + server := echoAPI.StrictServer{} + strictHandler := echoAPI.NewStrictHandler(server, nil) e := echo.New() - api4.RegisterHandlers(e, strictHandler) + echoAPI.RegisterHandlers(e, strictHandler) testImpl(t, e) } func TestGinServer(t *testing.T) { - server := api2.StrictServer{} - strictHandler := api2.NewStrictHandler(server, nil) + server := ginAPI.StrictServer{} + strictHandler := ginAPI.NewStrictHandler(server, nil) gin.SetMode(gin.ReleaseMode) r := gin.New() - api2.RegisterHandlers(r, strictHandler) + ginAPI.RegisterHandlers(r, strictHandler) testImpl(t, r) } func testImpl(t *testing.T, handler http.Handler) { t.Run("JSONExample", func(t *testing.T) { value := "123" - requestBody := api3.Example{Value: &value} + requestBody := clientAPI.Example{Value: &value} rr := testutil.NewRequest().Post("/json").WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder assert.Equal(t, http.StatusOK, rr.Code) assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) - var responseBody api3.Example + var responseBody clientAPI.Example err := json.NewDecoder(rr.Body).Decode(&responseBody) assert.NoError(t, err) assert.Equal(t, requestBody, responseBody) }) t.Run("URLEncodedExample", func(t *testing.T) { value := "456" - requestBody := api3.Example{Value: &value} + requestBody := clientAPI.Example{Value: &value} requestBodyEncoded, err := runtime.MarshalForm(&requestBody, nil) assert.NoError(t, err) rr := testutil.NewRequest().Post("/urlencoded").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(requestBodyEncoded.Encode())).GoWithHTTPHandler(t, handler).Recorder @@ -71,7 +82,7 @@ func testImpl(t *testing.T, handler http.Handler) { assert.Equal(t, "application/x-www-form-urlencoded", rr.Header().Get("Content-Type")) values, err := url.ParseQuery(rr.Body.String()) assert.NoError(t, err) - var responseBody api3.Example + var responseBody clientAPI.Example err = runtime.BindForm(&responseBody, values, nil, nil) assert.NoError(t, err) assert.Equal(t, requestBody, responseBody) @@ -100,6 +111,30 @@ func testImpl(t *testing.T, handler http.Handler) { _, err = reader.NextPart() assert.Equal(t, io.EOF, err) }) + t.Run("MultipartRelatedExample", func(t *testing.T) { + value := "789" + fieldName := "value" + var writer bytes.Buffer + mw := multipart.NewWriter(&writer) + field, err := mw.CreateFormField(fieldName) + assert.NoError(t, err) + _, _ = field.Write([]byte(value)) + assert.NoError(t, mw.Close()) + rr := testutil.NewRequest().Post("/multipart-related").WithContentType(mime.FormatMediaType("multipart/related", map[string]string{"boundary": mw.Boundary()})).WithBody(writer.Bytes()).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + contentType, params, err := mime.ParseMediaType(rr.Header().Get("Content-Type")) + assert.NoError(t, err) + assert.Equal(t, "multipart/related", contentType) + reader := multipart.NewReader(rr.Body, params["boundary"]) + part, err := reader.NextPart() + assert.NoError(t, err) + assert.Equal(t, part.FormName(), fieldName) + readValue, err := io.ReadAll(part) + assert.NoError(t, err) + assert.Equal(t, value, string(readValue)) + _, err = reader.NextPart() + assert.Equal(t, io.EOF, err) + }) t.Run("TextExample", func(t *testing.T) { value := "text" rr := testutil.NewRequest().Post("/text").WithContentType("text/plain").WithBody([]byte(value)).GoWithHTTPHandler(t, handler).Recorder @@ -116,18 +151,18 @@ func testImpl(t *testing.T, handler http.Handler) { }) t.Run("MultipleRequestAndResponseTypesJSON", func(t *testing.T) { value := "123" - requestBody := api3.Example{Value: &value} + requestBody := clientAPI.Example{Value: &value} rr := testutil.NewRequest().Post("/multiple").WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder assert.Equal(t, http.StatusOK, rr.Code) assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) - var responseBody api3.Example + var responseBody clientAPI.Example err := json.NewDecoder(rr.Body).Decode(&responseBody) assert.NoError(t, err) assert.Equal(t, requestBody, responseBody) }) t.Run("MultipleRequestAndResponseTypesFormdata", func(t *testing.T) { value := "456" - requestBody := api3.Example{Value: &value} + requestBody := clientAPI.Example{Value: &value} requestBodyEncoded, err := runtime.MarshalForm(&requestBody, nil) assert.NoError(t, err) rr := testutil.NewRequest().Post("/multiple").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(requestBodyEncoded.Encode())).GoWithHTTPHandler(t, handler).Recorder @@ -135,7 +170,7 @@ func testImpl(t *testing.T, handler http.Handler) { assert.Equal(t, "application/x-www-form-urlencoded", rr.Header().Get("Content-Type")) values, err := url.ParseQuery(rr.Body.String()) assert.NoError(t, err) - var responseBody api3.Example + var responseBody clientAPI.Example err = runtime.BindForm(&responseBody, values, nil, nil) assert.NoError(t, err) assert.Equal(t, requestBody, responseBody) @@ -182,11 +217,11 @@ func testImpl(t *testing.T, handler http.Handler) { header1 := "value1" header2 := "890" value := "asdf" - requestBody := api3.Example{Value: &value} + requestBody := clientAPI.Example{Value: &value} rr := testutil.NewRequest().Post("/with-headers").WithHeader("header1", header1).WithHeader("header2", header2).WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder assert.Equal(t, http.StatusOK, rr.Code) assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) - var responseBody api3.Example + var responseBody clientAPI.Example err := json.NewDecoder(rr.Body).Decode(&responseBody) assert.NoError(t, err) assert.Equal(t, requestBody, responseBody) @@ -203,11 +238,22 @@ func testImpl(t *testing.T, handler http.Handler) { }) t.Run("ReusableResponses", func(t *testing.T) { value := "jkl;" - requestBody := api3.Example{Value: &value} + requestBody := clientAPI.Example{Value: &value} rr := testutil.NewRequest().Post("/reusable-responses").WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder assert.Equal(t, http.StatusOK, rr.Code) assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) - var responseBody api3.Example + var responseBody clientAPI.Example + err := json.NewDecoder(rr.Body).Decode(&responseBody) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + }) + t.Run("UnionResponses", func(t *testing.T) { + value := "union" + requestBody := clientAPI.Example{Value: &value} + rr := testutil.NewRequest().Post("/with-union").WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) + var responseBody clientAPI.Example err := json.NewDecoder(rr.Body).Decode(&responseBody) assert.NoError(t, err) assert.Equal(t, requestBody, responseBody) diff --git a/pkg/chi-middleware/oapi_validate.go b/pkg/chi-middleware/oapi_validate.go deleted file mode 100644 index c09c2f1e28..0000000000 --- a/pkg/chi-middleware/oapi_validate.go +++ /dev/null @@ -1,139 +0,0 @@ -// Package middleware implements middleware function for go-chi or net/http, -// which validates incoming HTTP requests to make sure that they conform to the given OAPI 3.0 specification. -// When OAPI validation failes on the request, we return an HTTP/400. -package middleware - -import ( - "context" - "errors" - "fmt" - "log" - "net/http" - "strings" - - "github.com/getkin/kin-openapi/openapi3" - "github.com/getkin/kin-openapi/openapi3filter" - "github.com/getkin/kin-openapi/routers" - "github.com/getkin/kin-openapi/routers/gorillamux" -) - -// ErrorHandler is called when there is an error in validation -type ErrorHandler func(w http.ResponseWriter, message string, statusCode int) - -// MultiErrorHandler is called when oapi returns a MultiError type -type MultiErrorHandler func(openapi3.MultiError) (int, error) - -// Options to customize request validation, openapi3filter specified options will be passed through. -type Options struct { - Options openapi3filter.Options - ErrorHandler ErrorHandler - MultiErrorHandler MultiErrorHandler - // SilenceServersWarning allows silencing a warning for https://github.com/deepmap/oapi-codegen/issues/882 that reports when an OpenAPI spec has `spec.Servers != nil` - SilenceServersWarning bool -} - -// OapiRequestValidator Creates middleware to validate request by swagger spec. -// This middleware is good for net/http either since go-chi is 100% compatible with net/http. -func OapiRequestValidator(swagger *openapi3.T) func(next http.Handler) http.Handler { - return OapiRequestValidatorWithOptions(swagger, nil) -} - -// OapiRequestValidatorWithOptions Creates middleware to validate request by swagger spec. -// This middleware is good for net/http either since go-chi is 100% compatible with net/http. -func OapiRequestValidatorWithOptions(swagger *openapi3.T, options *Options) func(next http.Handler) http.Handler { - if swagger.Servers != nil && (options == nil || options.SilenceServersWarning) { - log.Println("WARN: OapiRequestValidatorWithOptions called with an OpenAPI spec that has `Servers` set. This may lead to an HTTP 400 with `no matching operation was found` when sending a valid request, as the validator performs `Host` header validation. If you're expecting `Host` header validation, you can silence this warning by setting `Options.SilenceServersWarning = true`. See https://github.com/deepmap/oapi-codegen/issues/882 for more information.") - } - - router, err := gorillamux.NewRouter(swagger) - if err != nil { - panic(err) - } - - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - // validate request - if statusCode, err := validateRequest(r, router, options); err != nil { - if options != nil && options.ErrorHandler != nil { - options.ErrorHandler(w, err.Error(), statusCode) - } else { - http.Error(w, err.Error(), statusCode) - } - return - } - - // serve - next.ServeHTTP(w, r) - }) - } - -} - -// This function is called from the middleware above and actually does the work -// of validating a request. -func validateRequest(r *http.Request, router routers.Router, options *Options) (int, error) { - - // Find route - route, pathParams, err := router.FindRoute(r) - if err != nil { - return http.StatusBadRequest, err // We failed to find a matching route for the request. - } - - // Validate request - requestValidationInput := &openapi3filter.RequestValidationInput{ - Request: r, - PathParams: pathParams, - Route: route, - } - - if options != nil { - requestValidationInput.Options = &options.Options - } - - if err := openapi3filter.ValidateRequest(context.Background(), requestValidationInput); err != nil { - me := openapi3.MultiError{} - if errors.As(err, &me) { - errFunc := getMultiErrorHandlerFromOptions(options) - return errFunc(me) - } - - switch e := err.(type) { - case *openapi3filter.RequestError: - // We've got a bad request - // Split up the verbose error by lines and return the first one - // openapi errors seem to be multi-line with a decent message on the first - errorLines := strings.Split(e.Error(), "\n") - return http.StatusBadRequest, fmt.Errorf(errorLines[0]) - case *openapi3filter.SecurityRequirementsError: - return http.StatusUnauthorized, err - default: - // This should never happen today, but if our upstream code changes, - // we don't want to crash the server, so handle the unexpected error. - return http.StatusInternalServerError, fmt.Errorf("error validating route: %s", err.Error()) - } - } - - return http.StatusOK, nil -} - -// attempt to get the MultiErrorHandler from the options. If it is not set, -// return a default handler -func getMultiErrorHandlerFromOptions(options *Options) MultiErrorHandler { - if options == nil { - return defaultMultiErrorHandler - } - - if options.MultiErrorHandler == nil { - return defaultMultiErrorHandler - } - - return options.MultiErrorHandler -} - -// defaultMultiErrorHandler returns a StatusBadRequest (400) and a list -// of all the errors. This method is called if there are no other -// methods defined on the options. -func defaultMultiErrorHandler(me openapi3.MultiError) (int, error) { - return http.StatusBadRequest, me -} diff --git a/pkg/chi-middleware/oapi_validate_test.go b/pkg/chi-middleware/oapi_validate_test.go deleted file mode 100644 index d10df5cbc9..0000000000 --- a/pkg/chi-middleware/oapi_validate_test.go +++ /dev/null @@ -1,405 +0,0 @@ -package middleware - -import ( - "context" - _ "embed" - "errors" - "io" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/deepmap/oapi-codegen/pkg/testutil" - "github.com/getkin/kin-openapi/openapi3" - "github.com/getkin/kin-openapi/openapi3filter" - "github.com/go-chi/chi/v5" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -//go:embed test_spec.yaml -var testSchema []byte - -func doGet(t *testing.T, mux *chi.Mux, rawURL string) *httptest.ResponseRecorder { - u, err := url.Parse(rawURL) - if err != nil { - t.Fatalf("Invalid url: %s", rawURL) - } - - response := testutil.NewRequest().Get(u.RequestURI()).WithHost(u.Host).WithAcceptJson().GoWithHTTPHandler(t, mux) - return response.Recorder -} - -func doPost(t *testing.T, mux *chi.Mux, rawURL string, jsonBody interface{}) *httptest.ResponseRecorder { - u, err := url.Parse(rawURL) - if err != nil { - t.Fatalf("Invalid url: %s", rawURL) - } - - response := testutil.NewRequest().Post(u.RequestURI()).WithHost(u.Host).WithJsonBody(jsonBody).GoWithHTTPHandler(t, mux) - return response.Recorder -} - -func TestOapiRequestValidator(t *testing.T) { - swagger, err := openapi3.NewLoader().LoadFromData(testSchema) - require.NoError(t, err, "Error initializing swagger") - - r := chi.NewRouter() - - // register middleware - r.Use(OapiRequestValidator(swagger)) - - // basic cases - testRequestValidatorBasicFunctions(t, r) -} - -func TestOapiRequestValidatorWithOptionsMultiError(t *testing.T) { - swagger, err := openapi3.NewLoader().LoadFromData(testSchema) - require.NoError(t, err, "Error initializing swagger") - - r := chi.NewRouter() - - // Set up an authenticator to check authenticated function. It will allow - // access to "someScope", but disallow others. - options := Options{ - Options: openapi3filter.Options{ - ExcludeRequestBody: false, - ExcludeResponseBody: false, - IncludeResponseStatus: true, - MultiError: true, - }, - } - - // register middleware - r.Use(OapiRequestValidatorWithOptions(swagger, &options)) - - called := false - - // Install a request handler for /resource. We want to make sure it doesn't - // get called. - r.Get("/multiparamresource", func(w http.ResponseWriter, r *http.Request) { - called = true - }) - - // Let's send a good request, it should pass - { - rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=50&id2=50") - assert.Equal(t, http.StatusOK, rec.Code) - assert.True(t, called, "Handler should have been called") - called = false - } - - // Let's send a request with a missing parameter, it should return - // a bad status - { - rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=50") - assert.Equal(t, http.StatusBadRequest, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "parameter \"id2\"") - assert.Contains(t, string(body), "value is required but missing") - } - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Let's send a request with a 2 missing parameters, it should return - // a bad status - { - rec := doGet(t, r, "http://deepmap.ai/multiparamresource") - assert.Equal(t, http.StatusBadRequest, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "parameter \"id\"") - assert.Contains(t, string(body), "value is required but missing") - assert.Contains(t, string(body), "parameter \"id2\"") - assert.Contains(t, string(body), "value is required but missing") - } - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Let's send a request with a 1 missing parameter, and another outside - // or the parameters. It should return a bad status - { - rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=500") - assert.Equal(t, http.StatusBadRequest, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "parameter \"id\"") - assert.Contains(t, string(body), "number must be at most 100") - assert.Contains(t, string(body), "parameter \"id2\"") - assert.Contains(t, string(body), "value is required but missing") - } - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Let's send a request with a parameters that do not meet spec. It should - // return a bad status - { - rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=abc&id2=1") - assert.Equal(t, http.StatusBadRequest, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "parameter \"id\"") - assert.Contains(t, string(body), "value abc: an invalid integer: invalid syntax") - assert.Contains(t, string(body), "parameter \"id2\"") - assert.Contains(t, string(body), "number must be at least 10") - } - assert.False(t, called, "Handler should not have been called") - called = false - } -} - -func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) { - swagger, err := openapi3.NewLoader().LoadFromData(testSchema) - require.NoError(t, err, "Error initializing swagger") - - r := chi.NewRouter() - - // Set up an authenticator to check authenticated function. It will allow - // access to "someScope", but disallow others. - options := Options{ - Options: openapi3filter.Options{ - ExcludeRequestBody: false, - ExcludeResponseBody: false, - IncludeResponseStatus: true, - MultiError: true, - }, - MultiErrorHandler: func(me openapi3.MultiError) (int, error) { - return http.StatusTeapot, me - }, - } - - // register middleware - r.Use(OapiRequestValidatorWithOptions(swagger, &options)) - - called := false - - // Install a request handler for /resource. We want to make sure it doesn't - // get called. - r.Get("/multiparamresource", func(w http.ResponseWriter, r *http.Request) { - called = true - }) - - // Let's send a good request, it should pass - { - rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=50&id2=50") - assert.Equal(t, http.StatusOK, rec.Code) - assert.True(t, called, "Handler should have been called") - called = false - } - - // Let's send a request with a missing parameter, it should return - // a bad status - { - rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=50") - assert.Equal(t, http.StatusTeapot, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "parameter \"id2\"") - assert.Contains(t, string(body), "value is required but missing") - } - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Let's send a request with a 2 missing parameters, it should return - // a bad status - { - rec := doGet(t, r, "http://deepmap.ai/multiparamresource") - assert.Equal(t, http.StatusTeapot, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "parameter \"id\"") - assert.Contains(t, string(body), "value is required but missing") - assert.Contains(t, string(body), "parameter \"id2\"") - assert.Contains(t, string(body), "value is required but missing") - } - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Let's send a request with a 1 missing parameter, and another outside - // or the parameters. It should return a bad status - { - rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=500") - assert.Equal(t, http.StatusTeapot, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "parameter \"id\"") - assert.Contains(t, string(body), "number must be at most 100") - assert.Contains(t, string(body), "parameter \"id2\"") - assert.Contains(t, string(body), "value is required but missing") - } - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Let's send a request with a parameters that do not meet spec. It should - // return a bad status - { - rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=abc&id2=1") - assert.Equal(t, http.StatusTeapot, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "parameter \"id\"") - assert.Contains(t, string(body), "value abc: an invalid integer: invalid syntax") - assert.Contains(t, string(body), "parameter \"id2\"") - assert.Contains(t, string(body), "number must be at least 10") - } - assert.False(t, called, "Handler should not have been called") - called = false - } -} - -func TestOapiRequestValidatorWithOptions(t *testing.T) { - swagger, err := openapi3.NewLoader().LoadFromData(testSchema) - require.NoError(t, err, "Error initializing swagger") - - r := chi.NewRouter() - - // Set up an authenticator to check authenticated function. It will allow - // access to "someScope", but disallow others. - options := Options{ - ErrorHandler: func(w http.ResponseWriter, message string, statusCode int) { - http.Error(w, "test: "+message, statusCode) - }, - Options: openapi3filter.Options{ - AuthenticationFunc: func(c context.Context, input *openapi3filter.AuthenticationInput) error { - - for _, s := range input.Scopes { - if s == "someScope" { - return nil - } - } - return errors.New("unauthorized") - }, - }, - } - - // register middleware - r.Use(OapiRequestValidatorWithOptions(swagger, &options)) - - // basic cases - testRequestValidatorBasicFunctions(t, r) - - called := false - - r.Get("/protected_resource", func(w http.ResponseWriter, r *http.Request) { - called = true - w.WriteHeader(http.StatusNoContent) - }) - - // Call a protected function to which we have access - { - rec := doGet(t, r, "http://deepmap.ai/protected_resource") - assert.Equal(t, http.StatusNoContent, rec.Code) - assert.True(t, called, "Handler should have been called") - called = false - } - - r.Get("/protected_resource2", func(w http.ResponseWriter, r *http.Request) { - called = true - w.WriteHeader(http.StatusNoContent) - }) - // Call a protected function to which we dont have access - { - rec := doGet(t, r, "http://deepmap.ai/protected_resource2") - assert.Equal(t, http.StatusUnauthorized, rec.Code) - assert.False(t, called, "Handler should not have been called") - called = false - } - - r.Get("/protected_resource_401", func(w http.ResponseWriter, r *http.Request) { - called = true - w.WriteHeader(http.StatusNoContent) - }) - // Call a protected function without credentials - { - rec := doGet(t, r, "http://deepmap.ai/protected_resource_401") - assert.Equal(t, http.StatusUnauthorized, rec.Code) - assert.Equal(t, "test: security requirements failed: unauthorized\n", rec.Body.String()) - assert.False(t, called, "Handler should not have been called") - called = false - } - -} - -func testRequestValidatorBasicFunctions(t *testing.T, r *chi.Mux) { - - called := false - - // Install a request handler for /resource. We want to make sure it doesn't - // get called. - r.Get("/resource", func(w http.ResponseWriter, r *http.Request) { - called = true - }) - - // Let's send the request to the wrong server, this should fail validation - { - rec := doGet(t, r, "http://not.deepmap.ai/resource") - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.False(t, called, "Handler should not have been called") - } - - // Let's send a good request, it should pass - { - rec := doGet(t, r, "http://deepmap.ai/resource") - assert.Equal(t, http.StatusOK, rec.Code) - assert.True(t, called, "Handler should have been called") - called = false - } - - // Send an out-of-spec parameter - { - rec := doGet(t, r, "http://deepmap.ai/resource?id=500") - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Send a bad parameter type - { - rec := doGet(t, r, "http://deepmap.ai/resource?id=foo") - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Add a handler for the POST message - r.Post("/resource", func(w http.ResponseWriter, r *http.Request) { - called = true - w.WriteHeader(http.StatusNoContent) - }) - - called = false - // Send a good request body - { - body := struct { - Name string `json:"name"` - }{ - Name: "Marcin", - } - rec := doPost(t, r, "http://deepmap.ai/resource", body) - assert.Equal(t, http.StatusNoContent, rec.Code) - assert.True(t, called, "Handler should have been called") - called = false - } - - // Send a malformed body - { - body := struct { - Name int `json:"name"` - }{ - Name: 7, - } - rec := doPost(t, r, "http://deepmap.ai/resource", body) - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.False(t, called, "Handler should not have been called") - called = false - } - -} diff --git a/pkg/chi-middleware/test_spec.yaml b/pkg/chi-middleware/test_spec.yaml deleted file mode 100644 index 6e0a2415d2..0000000000 --- a/pkg/chi-middleware/test_spec.yaml +++ /dev/null @@ -1,103 +0,0 @@ -openapi: "3.0.0" -info: - version: 1.0.0 - title: TestServer -servers: - - url: http://deepmap.ai/ -paths: - /resource: - get: - operationId: getResource - parameters: - - name: id - in: query - schema: - type: integer - minimum: 10 - maximum: 100 - responses: - '200': - description: success - content: - application/json: - schema: - properties: - name: - type: string - id: - type: integer - post: - operationId: createResource - responses: - '204': - description: No content - requestBody: - required: true - content: - application/json: - schema: - properties: - name: - type: string - /protected_resource: - get: - operationId: getProtectedResource - security: - - BearerAuth: - - someScope - responses: - '204': - description: no content - /protected_resource2: - get: - operationId: getProtectedResource - security: - - BearerAuth: - - otherScope - responses: - '204': - description: no content - /protected_resource_401: - get: - operationId: getProtectedResource - security: - - BearerAuth: - - unauthorized - responses: - '401': - description: no content - /multiparamresource: - get: - operationId: getResource - parameters: - - name: id - in: query - required: true - schema: - type: integer - minimum: 10 - maximum: 100 - - name: id2 - required: true - in: query - schema: - type: integer - minimum: 10 - maximum: 100 - responses: - '200': - description: success - content: - application/json: - schema: - properties: - name: - type: string - id: - type: integer -components: - securitySchemes: - BearerAuth: - type: http - scheme: bearer - bearerFormat: JWT diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go index 956d589329..3a77729bff 100644 --- a/pkg/codegen/codegen.go +++ b/pkg/codegen/codegen.go @@ -17,16 +17,25 @@ package codegen import ( "bufio" "bytes" + "context" "embed" + "errors" "fmt" + "go/scanner" + "io" "io/fs" + "net/http" + "os" "runtime/debug" "sort" "strings" "text/template" + "time" "github.com/getkin/kin-openapi/openapi3" "golang.org/x/tools/imports" + + "github.com/oapi-codegen/oapi-codegen/v2/pkg/util" ) // Embed the templates directory @@ -37,8 +46,20 @@ var templates embed.FS // globalState stores all global state. Please don't put global state anywhere // else so that we can easily track it. var globalState struct { - options Configuration - spec *openapi3.T + options Configuration + spec *openapi3.T + importMapping importMap + // initialismsMap stores initialisms as "lower(initialism) -> initialism" map. + // List of initialisms was taken from https://staticcheck.io/docs/configuration/options/#initialisms. + initialismsMap map[string]string + // typeMapping is the merged type mapping (defaults + user overrides). + typeMapping TypeMapping + // resolvedNames maps schema path strings (e.g. "components/schemas/Pet") + // to their resolved Go type names, assigned by the multi-pass name resolver. + resolvedNames map[string]string + // resolvedClientWrapperNames maps operationID to the resolved Go type name + // for client response wrapper types (e.g., "createChatCompletion" -> "CreateChatCompletionResponseWrapper"). + resolvedClientWrapperNames map[string]string } // goImport represents a go package to be imported in the generated code @@ -58,21 +79,27 @@ func (gi goImport) String() string { // importMap maps external OpenAPI specifications files/urls to external go packages type importMap map[string]goImport +// importMappingCurrentPackage allows an Import Mapping to map to the current package, rather than an external package. +// This allows users to split their OpenAPI specification across multiple files, but keep them in the same package, which can reduce a bit of the overhead for users. +// We use `-` to indicate that this is a bit of a special case +const importMappingCurrentPackage = "-" + // GoImports returns a slice of go import statements func (im importMap) GoImports() []string { goImports := make([]string, 0, len(im)) for _, v := range im { + if v.Path == importMappingCurrentPackage { + continue + } goImports = append(goImports, v.String()) } return goImports } -var importMapping importMap - func constructImportMapping(importMapping map[string]string) importMap { var ( - pathToName = map[string]string{} - result = importMap{} + pathToImport = importMap{} + result = importMap{} ) { @@ -83,13 +110,32 @@ func constructImportMapping(importMapping map[string]string) importMap { sort.Strings(packagePaths) for _, packagePath := range packagePaths { - if _, ok := pathToName[packagePath]; !ok { - pathToName[packagePath] = fmt.Sprintf("externalRef%d", len(pathToName)) + if _, ok := pathToImport[packagePath]; !ok && packagePath != importMappingCurrentPackage { + split := strings.Split(packagePath, " ") + if len(split) == 2 { + // if we have 2 parts, we assume both the package name and path are provided, and we use them as is + pathToImport[packagePath] = goImport{ + Name: split[0], + Path: split[1], + } + } else { + // otherwise, we auto-generate a package name based on the order of the imports, to ensure deterministic output + pathToImport[packagePath] = goImport{ + Name: fmt.Sprintf("externalRef%d", len(pathToImport)), + Path: packagePath, + } + } } } } for specPath, packagePath := range importMapping { - result[specPath] = goImport{Name: pathToName[packagePath], Path: packagePath} + if packagePath == importMappingCurrentPackage { + result[specPath] = goImport{ + Path: importMappingCurrentPackage, + } + } else { + result[specPath] = pathToImport[packagePath] + } } return result } @@ -101,10 +147,15 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { // This is global state globalState.options = opts globalState.spec = spec - - importMapping = constructImportMapping(opts.ImportMapping) + globalState.importMapping = constructImportMapping(opts.ImportMapping) + if opts.OutputOptions.TypeMapping != nil { + globalState.typeMapping = DefaultTypeMapping.Merge(*opts.OutputOptions.TypeMapping) + } else { + globalState.typeMapping = DefaultTypeMapping + } filterOperationsByTag(spec, opts) + filterOperationsByOperationID(spec, opts) if !opts.OutputOptions.SkipPrune { pruneUnusedComponents(spec) } @@ -118,6 +169,41 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { globalState.options.OutputOptions.ClientTypeName = defaultClientTypeName } + nameNormalizerFunction := NameNormalizerFunction(opts.OutputOptions.NameNormalizer) + nameNormalizer = NameNormalizers[nameNormalizerFunction] + if nameNormalizer == nil { + return "", fmt.Errorf(`the name-normalizer option %v could not be found among options %q`, + opts.OutputOptions.NameNormalizer, NameNormalizers.Options()) + } + + if nameNormalizerFunction != NameNormalizerFunctionToCamelCaseWithInitialisms && len(opts.OutputOptions.AdditionalInitialisms) > 0 { + return "", fmt.Errorf("you have specified `additional-initialisms`, but the `name-normalizer` is not set to `ToCamelCaseWithInitialisms`. Please specify `name-normalizer: ToCamelCaseWithInitialisms` or remove the `additional-initialisms` configuration") + } + + globalState.initialismsMap = makeInitialismsMap(opts.OutputOptions.AdditionalInitialisms) + + // Multi-pass name resolution: gather all schemas, then resolve names globally. + // Only enabled when resolve-type-name-collisions is set. + if opts.OutputOptions.ResolveTypeNameCollisions { + gathered := GatherSchemas(spec, opts) + globalState.resolvedNames = ResolveNames(gathered) + // Build a separate operationID -> wrapper name lookup for genResponseTypeName. + // Keys must use the normalized operationID (via nameNormalizer) because + // OperationDefinition.OperationId is normalized before templates run. + globalState.resolvedClientWrapperNames = make(map[string]string) + for _, gs := range gathered { + if gs.Context == ContextClientResponseWrapper && gs.OperationID != "" { + if name, ok := globalState.resolvedNames[gs.Path.String()]; ok { + normalizedOpID := nameNormalizer(gs.OperationID) + globalState.resolvedClientWrapperNames[normalizedOpID] = name + } + } + } + } else { + globalState.resolvedNames = nil + globalState.resolvedClientWrapperNames = nil + } + // This creates the golang templates text package TemplateFunctions["opts"] = func() Configuration { return globalState.options } t := template.New("oapi-codegen").Funcs(TemplateFunctions) @@ -128,17 +214,22 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { return "", fmt.Errorf("error parsing oapi-codegen templates: %w", err) } - // Override built-in templates with user-provided versions - for _, tpl := range t.Templates() { - if _, ok := opts.OutputOptions.UserTemplates[tpl.Name()]; ok { - utpl := t.New(tpl.Name()) - if _, err := utpl.Parse(opts.OutputOptions.UserTemplates[tpl.Name()]); err != nil { - return "", fmt.Errorf("error parsing user-provided template %q: %w", tpl.Name(), err) - } + // load user-provided templates. Will Override built-in versions. + for name, template := range opts.OutputOptions.UserTemplates { + utpl := t.New(name) + + txt, err := GetUserTemplateText(template) + if err != nil { + return "", fmt.Errorf("error loading user-provided template %q: %w", name, err) + } + + _, err = utpl.Parse(txt) + if err != nil { + return "", fmt.Errorf("error parsing user-provided template %q: %w", name, err) } } - ops, err := OperationDefinitions(spec) + ops, err := OperationDefinitions(spec, opts.OutputOptions.InitialismOverrides) if err != nil { return "", fmt.Errorf("error creating operation definitions: %w", err) } @@ -167,6 +258,22 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { MergeImports(xGoTypeImports, imprts) } + var serverURLsDefinitions string + if opts.Generate.ServerURLs { + serverURLsDefinitions, err = GenerateServerURLs(t, spec) + if err != nil { + return "", fmt.Errorf("error generating Server URLs: %w", err) + } + } + + var irisServerOut string + if opts.Generate.IrisServer { + irisServerOut, err = GenerateIrisServer(t, ops) + if err != nil { + return "", fmt.Errorf("error generating Go handlers for Paths: %w", err) + } + } + var echoServerOut string if opts.Generate.EchoServer { echoServerOut, err = GenerateEchoServer(t, ops) @@ -175,6 +282,14 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { } } + var echo5ServerOut string + if opts.Generate.Echo5Server { + echo5ServerOut, err = GenerateEcho5Server(t, ops) + if err != nil { + return "", fmt.Errorf("error generating Go handlers for Paths: %w", err) + } + } + var chiServerOut string if opts.Generate.ChiServer { chiServerOut, err = GenerateChiServer(t, ops) @@ -183,6 +298,14 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { } } + var fiberServerOut string + if opts.Generate.FiberServer { + fiberServerOut, err = GenerateFiberServer(t, ops) + if err != nil { + return "", fmt.Errorf("error generating Go handlers for Paths: %w", err) + } + } + var ginServerOut string if opts.Generate.GinServer { ginServerOut, err = GenerateGinServer(t, ops) @@ -199,6 +322,14 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { } } + var stdHTTPServerOut string + if opts.Generate.StdHTTPServer { + stdHTTPServerOut, err = GenerateStdHTTPServer(t, ops) + if err != nil { + return "", fmt.Errorf("error generating Go handlers for Paths: %w", err) + } + } + var strictServerOut string if opts.Generate.Strict { var responses []ResponseDefinition @@ -237,7 +368,7 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { var inlinedSpec string if opts.Generate.EmbeddedSpec { - inlinedSpec, err = GenerateInlinedSpec(t, importMapping, spec) + inlinedSpec, err = GenerateInlinedSpec(t, globalState.importMapping, spec) if err != nil { return "", fmt.Errorf("error generating Go handlers for Paths: %w", err) } @@ -246,8 +377,13 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { var buf bytes.Buffer w := bufio.NewWriter(&buf) - externalImports := append(importMapping.GoImports(), importMap(xGoTypeImports).GoImports()...) - importsOut, err := GenerateImports(t, externalImports, opts.PackageName) + externalImports := append(globalState.importMapping.GoImports(), importMap(xGoTypeImports).GoImports()...) + importsOut, err := GenerateImports( + t, + externalImports, + opts.PackageName, + opts.NoVCSVersionOverride, + ) if err != nil { return "", fmt.Errorf("error generating imports: %w", err) } @@ -262,6 +398,11 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { return "", fmt.Errorf("error writing constants: %w", err) } + _, err = w.WriteString(serverURLsDefinitions) + if err != nil { + return "", fmt.Errorf("error writing Server URLs: %w", err) + } + _, err = w.WriteString(typeDefinitions) if err != nil { return "", fmt.Errorf("error writing type definitions: %w", err) @@ -278,6 +419,14 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { } } + if opts.Generate.IrisServer { + _, err = w.WriteString(irisServerOut) + if err != nil { + return "", fmt.Errorf("error writing server path handlers: %w", err) + } + + } + if opts.Generate.EchoServer { _, err = w.WriteString(echoServerOut) if err != nil { @@ -285,6 +434,13 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { } } + if opts.Generate.Echo5Server { + _, err = w.WriteString(echo5ServerOut) + if err != nil { + return "", fmt.Errorf("error writing server path handlers: %w", err) + } + } + if opts.Generate.ChiServer { _, err = w.WriteString(chiServerOut) if err != nil { @@ -292,6 +448,13 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { } } + if opts.Generate.FiberServer { + _, err = w.WriteString(fiberServerOut) + if err != nil { + return "", fmt.Errorf("error writing server path handlers: %w", err) + } + } + if opts.Generate.GinServer { _, err = w.WriteString(ginServerOut) if err != nil { @@ -306,6 +469,13 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { } } + if opts.Generate.StdHTTPServer { + _, err = w.WriteString(stdHTTPServerOut) + if err != nil { + return "", fmt.Errorf("error writing server path handlers: %w", err) + } + } + if opts.Generate.Strict { _, err = w.WriteString(strictServerOut) if err != nil { @@ -336,11 +506,36 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { outBytes, err := imports.Process(opts.PackageName+".go", []byte(goCode), nil) if err != nil { - return "", fmt.Errorf("error formatting Go code %s: %w", goCode, err) + // if we don't get a line number + errLine := -1 + var scanErr scanner.ErrorList + if errors.As(err, &scanErr) && scanErr.Len() > 0 { + // for now, only return the first error's information + errLine = scanErr[0].Pos.Line + } + return "", fmt.Errorf("error formatting Go code:\n%s\nerror was: %w", addLineNumbers(goCode, errLine), err) } return string(outBytes), nil } +func addLineNumbers(goCode string, lineWithError int) string { + var out []string + lines := strings.Split(goCode, "\n") + for i, line := range lines { + // lines for humans start at 1 + lineNumber := i + 1 + + errLine := " " + if lineNumber == lineWithError { + errLine = "❗" + } + + out = append(out, fmt.Sprintf("%s%5d: %s", errLine, lineNumber, line)) + } + + return strings.Join(out, "\n") +} + func GenerateTypeDefinitions(t *template.Template, swagger *openapi3.T, ops []OperationDefinition, excludeSchemas []string) (string, error) { var allTypes []TypeDefinition if swagger.Components != nil { @@ -462,13 +657,17 @@ func GenerateTypesForSchemas(t *template.Template, schemas map[string]*openapi3. return nil, fmt.Errorf("error making name for components/schemas/%s: %w", schemaName, err) } + if resolved := resolvedNameForComponent("schemas", schemaName); resolved != "" { + goTypeName = resolved + } + types = append(types, TypeDefinition{ JsonName: schemaName, TypeName: goTypeName, Schema: goSchema, }) - types = append(types, goSchema.GetAdditionalTypeDefs()...) + types = append(types, goSchema.AdditionalTypes...) } return types, nil } @@ -477,7 +676,7 @@ func GenerateTypesForSchemas(t *template.Template, schemas map[string]*openapi3. // components/parameters section of the Swagger spec. func GenerateTypesForParameters(t *template.Template, params map[string]*openapi3.ParameterRef) ([]TypeDefinition, error) { var types []TypeDefinition - for _, paramName := range SortedParameterKeys(params) { + for _, paramName := range SortedMapKeys(params) { paramOrRef := params[paramName] goType, err := paramToGoType(paramOrRef.Value, nil) @@ -490,6 +689,10 @@ func GenerateTypesForParameters(t *template.Template, params map[string]*openapi return nil, fmt.Errorf("error making name for components/parameters/%s: %w", paramName, err) } + if resolved := resolvedNameForComponent("parameters", paramName); resolved != "" { + goTypeName = resolved + } + typeDef := TypeDefinition{ JsonName: paramName, Schema: goType, @@ -512,19 +715,47 @@ func GenerateTypesForParameters(t *template.Template, params map[string]*openapi // GenerateTypesForResponses generates type definitions for any custom types defined in the // components/responses section of the Swagger spec. -func GenerateTypesForResponses(t *template.Template, responses openapi3.Responses) ([]TypeDefinition, error) { +func GenerateTypesForResponses(t *template.Template, responses openapi3.ResponseBodies) ([]TypeDefinition, error) { var types []TypeDefinition - for _, responseName := range SortedResponsesKeys(responses) { + for _, responseName := range SortedMapKeys(responses) { responseOrRef := responses[responseName] // We have to generate the response object. We're only going to - // handle application/json media types here. Other responses should + // handle media types that conform to JSON. Other responses should // simply be specified as strings or byte arrays. response := responseOrRef.Value - jsonResponse, found := response.Content["application/json"] - if found { - goType, err := GenerateGoSchema(jsonResponse.Schema, []string{responseName}) + + jsonCount := 0 + for mediaType := range response.Content { + if util.IsMediaTypeJson(mediaType) { + jsonCount++ + } + } + + SortedMapKeys := SortedMapKeys(response.Content) + for _, mediaType := range SortedMapKeys { + response := response.Content[mediaType] + if !util.IsMediaTypeJson(mediaType) { + continue + } + + // When a response has multiple JSON content types, include the + // content type in the schema path so that inline types (e.g., + // oneOf union members) get unique names per content type. + // See the matching logic in GetResponseTypeDefinitions. + // + // We only add the content type segment when collision resolution + // is enabled (resolve-type-name-collisions) and jsonCount > 1, + // to avoid changing type names for existing users. Ideally the + // media type would always be part of the path for consistency. + // TODO: revisit this at the next major version change — + // always include the media type in the schema path. + schemaPath := []string{responseName} + if jsonCount > 1 && globalState.options.OutputOptions.ResolveTypeNameCollisions { + schemaPath = append(schemaPath, mediaTypeToCamelCase(mediaType)) + } + goType, err := GenerateGoSchema(response.Schema, schemaPath) if err != nil { return nil, fmt.Errorf("error generating Go type for schema in response %s: %w", responseName, err) } @@ -534,6 +765,10 @@ func GenerateTypesForResponses(t *template.Template, responses openapi3.Response return nil, fmt.Errorf("error making name for components/responses/%s: %w", responseName, err) } + if resolved := resolvedNameForComponent("responses", responseName, mediaType); resolved != "" { + goTypeName = resolved + } + typeDef := TypeDefinition{ JsonName: responseName, Schema: goType, @@ -548,7 +783,13 @@ func GenerateTypesForResponses(t *template.Template, responses openapi3.Response } typeDef.TypeName = SchemaNameToTypeName(refType) } + + if jsonCount > 1 { + typeDef.TypeName = typeDef.TypeName + mediaTypeToCamelCase(mediaType) + } + types = append(types, typeDef) + types = append(types, goType.AdditionalTypes...) } } return types, nil @@ -559,15 +800,19 @@ func GenerateTypesForResponses(t *template.Template, responses openapi3.Response func GenerateTypesForRequestBodies(t *template.Template, bodies map[string]*openapi3.RequestBodyRef) ([]TypeDefinition, error) { var types []TypeDefinition - for _, requestBodyName := range SortedRequestBodyKeys(bodies) { + for _, requestBodyName := range SortedMapKeys(bodies) { requestBodyRef := bodies[requestBodyName] // As for responses, we will only generate Go code for JSON bodies, // the other body formats are up to the user. response := requestBodyRef.Value - jsonBody, found := response.Content["application/json"] - if found { - goType, err := GenerateGoSchema(jsonBody.Schema, []string{requestBodyName}) + for _, mediaType := range SortedMapKeys(response.Content) { + body := response.Content[mediaType] + if !util.IsMediaTypeJson(mediaType) { + continue + } + + goType, err := GenerateGoSchema(body.Schema, []string{requestBodyName}) if err != nil { return nil, fmt.Errorf("error generating Go type for schema in body %s: %w", requestBodyName, err) } @@ -577,6 +822,10 @@ func GenerateTypesForRequestBodies(t *template.Template, bodies map[string]*open return nil, fmt.Errorf("error making name for components/schemas/%s: %w", requestBodyName, err) } + if resolved := resolvedNameForComponent("requestBodies", requestBodyName, mediaType); resolved != "" { + goTypeName = resolved + } + typeDef := TypeDefinition{ JsonName: requestBodyName, Schema: goType, @@ -592,6 +841,7 @@ func GenerateTypesForRequestBodies(t *template.Template, bodies map[string]*open typeDef.TypeName = SchemaNameToTypeName(refType) } types = append(types, typeDef) + types = append(types, goType.AdditionalTypes...) } } return types, nil @@ -606,12 +856,11 @@ func GenerateTypes(t *template.Template, types []TypeDefinition) (string, error) for _, typ := range types { if prevType, found := m[typ.TypeName]; found { // If type names collide, we need to see if they refer to the same - // exact type definition, in which case, we can de-dupe. If they don't - // match, we error out. + // exact type definition, in which case, we can de-dupe. If they + // don't match, we error out. if TypeDefinitionsEquivalent(prevType, typ) { continue } - // We want to create an error when we try to define the same type twice. return "", fmt.Errorf("duplicate typename '%s' detected, can't auto-rename, "+ "please use x-go-name to specify your own name for one of them", typ.TypeName) } @@ -630,6 +879,63 @@ func GenerateTypes(t *template.Template, types []TypeDefinition) (string, error) return GenerateTemplates([]string{"typedef.tmpl"}, t, context) } +// resolvedNameForComponent looks up the resolved Go type name for a component +// identified by its section (e.g., "schemas", "parameters") and name. +// For content-bearing sections (responses, requestBodies), an optional +// contentType can be provided to match the exact media type entry. +// Returns empty string if no resolved name is available. +func resolvedNameForComponent(section, name string, contentType ...string) string { + if len(globalState.resolvedNames) == 0 { + return "" + } + + // Direct key match for schemas, parameters, headers + key := "components/" + section + "/" + name + if resolved, ok := globalState.resolvedNames[key]; ok { + return resolved + } + + // For responses and requestBodies, the path includes content type info. + // If a specific content type was provided, do an exact match. + if len(contentType) > 0 && contentType[0] != "" { + exactKey := key + "/content/" + contentType[0] + if resolved, ok := globalState.resolvedNames[exactKey]; ok { + return resolved + } + } + + // Fall back to prefix match for callers that don't specify content type. + // Sort matching keys so the result is deterministic across runs. + prefix := key + "/" + var matches []string + for k := range globalState.resolvedNames { + if strings.HasPrefix(k, prefix) { + matches = append(matches, k) + } + } + if len(matches) > 0 { + if len(matches) > 1 { + sort.Strings(matches) + } + return globalState.resolvedNames[matches[0]] + } + + return "" +} + +// resolvedNameForRefPath looks up the resolved Go type name for a $ref path +// like "#/components/responses/Foo", optionally scoped to a specific content type. +func resolvedNameForRefPath(refPath, contentType string) string { + if len(globalState.resolvedNames) == 0 || !strings.HasPrefix(refPath, "#/") { + return "" + } + parts := strings.Split(refPath, "/") + if len(parts) != 4 || parts[1] != "components" { + return "" + } + return resolvedNameForComponent(parts[2], parts[3], contentType) +} + func GenerateEnums(t *template.Template, types []TypeDefinition) (string, error) { enums := []EnumDefinition{} @@ -707,7 +1013,7 @@ func GenerateEnums(t *template.Template, types []TypeDefinition) (string, error) } // GenerateImports generates our import statements and package definition. -func GenerateImports(t *template.Template, externalImports []string, packageName string) (string, error) { +func GenerateImports(t *template.Template, externalImports []string, packageName string, versionOverride *string) (string, error) { // Read build version for incorporating into generated files // Unit tests have ok=false, so we'll just use "unknown" for the // version if we can't read this. @@ -721,6 +1027,9 @@ func GenerateImports(t *template.Template, externalImports []string, packageName if bi.Main.Version != "" { moduleVersion = bi.Main.Version } + if versionOverride != nil { + moduleVersion = *versionOverride + } } context := struct { @@ -729,12 +1038,14 @@ func GenerateImports(t *template.Template, externalImports []string, packageName ModuleName string Version string AdditionalImports []AdditionalImport + RouterImports []AdditionalImport }{ ExternalImports: externalImports, PackageName: packageName, ModuleName: modulePath, Version: moduleVersion, AdditionalImports: globalState.options.AdditionalImports, + RouterImports: globalState.options.Generate.RouterImports(), } return GenerateTemplates([]string{"imports.tmpl"}, t, context) @@ -814,7 +1125,59 @@ func GenerateUnionAndAdditionalProopertiesBoilerplate(t *template.Template, type func SanitizeCode(goCode string) string { // remove any byte-order-marks which break Go-Code // See: https://groups.google.com/forum/#!topic/golang-nuts/OToNIPdfkks - return strings.Replace(goCode, "\uFEFF", "", -1) + return strings.ReplaceAll(goCode, "\uFEFF", "") +} + +// GetUserTemplateText attempts to retrieve the template text from a passed in URL or file +// path when inputData is more than one line. +// This function will attempt to load a file first, and if it fails, will try to get the +// data from the remote endpoint. +// The timeout for remote download file is 30 seconds. +func GetUserTemplateText(inputData string) (template string, err error) { + // if the input data is more than one line, assume its a template and return that data. + if strings.Contains(inputData, "\n") { + return inputData, nil + } + + // load data from file + data, err := os.ReadFile(inputData) + // return data if found and loaded + if err == nil { + return string(data), nil + } + + // check for non "not found" errors + if !os.IsNotExist(err) { + return "", fmt.Errorf("failed to open file %s: %w", inputData, err) + } + + // attempt to get data from url with timeout + const downloadTimeout = 30 * time.Second + ctx, cancel := context.WithTimeout(context.Background(), downloadTimeout) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, inputData, http.NoBody) + if err != nil { + return "", fmt.Errorf("failed to create request GET %s: %w", inputData, err) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", fmt.Errorf("failed to execute GET request data from %s: %w", inputData, err) + } + if resp != nil { + defer func() { + _ = resp.Body.Close() + }() + } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return "", fmt.Errorf("got non %d status code on GET %s", resp.StatusCode, inputData) + } + data, err = io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("failed to read response body from GET %s: %w", inputData, err) + } + + return string(data), nil } // LoadTemplates loads all of our template files into a text/template. The @@ -945,8 +1308,7 @@ func GoSchemaImports(schemas ...*openapi3.SchemaRef) (map[string]goImport, error schemaVal := sref.Value t := schemaVal.Type - switch t { - case "", "object": + if t.Slice() == nil || t.Is("object") { for _, v := range schemaVal.Properties { imprts, err := GoSchemaImports(v) if err != nil { @@ -954,7 +1316,7 @@ func GoSchemaImports(schemas ...*openapi3.SchemaRef) (map[string]goImport, error } MergeImports(res, imprts) } - case "array": + } else if t.Is("array") { imprts, err := GoSchemaImports(schemaVal.Items) if err != nil { return nil, err @@ -989,9 +1351,12 @@ func GetRequestBodiesImports(bodies map[string]*openapi3.RequestBodyRef) (map[st res := map[string]goImport{} for _, r := range bodies { response := r.Value - jsonBody, found := response.Content["application/json"] - if found { - imprts, err := GoSchemaImports(jsonBody.Schema) + for mediaType, body := range response.Content { + if !util.IsMediaTypeJson(mediaType) { + continue + } + + imprts, err := GoSchemaImports(body.Schema) if err != nil { return nil, err } @@ -1005,9 +1370,12 @@ func GetResponsesImports(responses map[string]*openapi3.ResponseRef) (map[string res := map[string]goImport{} for _, r := range responses { response := r.Value - jsonResponse, found := response.Content["application/json"] - if found { - imprts, err := GoSchemaImports(jsonResponse.Schema) + for mediaType, body := range response.Content { + if !util.IsMediaTypeJson(mediaType) { + continue + } + + imprts, err := GoSchemaImports(body.Schema) if err != nil { return nil, err } @@ -1031,3 +1399,7 @@ func GetParametersImports(params map[string]*openapi3.ParameterRef) (map[string] } return res, nil } + +func SetGlobalStateSpec(spec *openapi3.T) { + globalState.spec = spec +} diff --git a/pkg/codegen/codegen_test.go b/pkg/codegen/codegen_test.go index e3878f58dd..b7b3184c3c 100644 --- a/pkg/codegen/codegen_test.go +++ b/pkg/codegen/codegen_test.go @@ -1,136 +1,23 @@ package codegen import ( - "bytes" _ "embed" "go/format" - "io" - "net/http" "testing" - examplePetstoreClient "github.com/deepmap/oapi-codegen/examples/petstore-expanded" - examplePetstore "github.com/deepmap/oapi-codegen/examples/petstore-expanded/echo/api" - "github.com/deepmap/oapi-codegen/pkg/util" "github.com/getkin/kin-openapi/openapi3" - "github.com/golangci/lint-1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/oapi-codegen/oapi-codegen/v2/pkg/util" ) const ( - remoteRefFile = `https://raw.githubusercontent.com/deepmap/oapi-codegen/master/examples/petstore-expanded` + + remoteRefFile = `https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/master/examples/petstore-expanded` + `/petstore-expanded.yaml` - remoteRefImport = `github.com/deepmap/oapi-codegen/examples/petstore-expanded` + remoteRefImport = `github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded` ) -func checkLint(t *testing.T, filename string, code []byte) { - linter := new(lint.Linter) - problems, err := linter.Lint(filename, code) - assert.NoError(t, err) - assert.Len(t, problems, 0) -} - -func TestExamplePetStoreCodeGeneration(t *testing.T) { - - // Input vars for code generation: - packageName := "api" - opts := Configuration{ - PackageName: packageName, - Generate: GenerateOptions{ - EchoServer: true, - Client: true, - Models: true, - EmbeddedSpec: true, - }, - } - - // Get a spec from the example PetStore definition: - swagger, err := examplePetstore.GetSwagger() - assert.NoError(t, err) - - // Run our code generation: - code, err := Generate(swagger, opts) - assert.NoError(t, err) - assert.NotEmpty(t, code) - - // Check that we have valid (formattable) code: - _, err = format.Source([]byte(code)) - assert.NoError(t, err) - - // Check that we have a package: - assert.Contains(t, code, "package api") - - // Check that the client method signatures return response structs: - assert.Contains(t, code, "func (c *Client) FindPetByID(ctx context.Context, id int64, reqEditors ...RequestEditorFn) (*http.Response, error) {") - - // Check that the property comments were generated - assert.Contains(t, code, "// Id Unique id of the pet") - - // Check that the summary comment contains newlines - assert.Contains(t, code, `// Deletes a pet by ID - // (DELETE /pets/{id}) -`) - - // Make sure the generated code is valid: - checkLint(t, "test.gen.go", []byte(code)) -} - -func TestExamplePetStoreCodeGenerationWithUserTemplates(t *testing.T) { - - userTemplates := map[string]string{"typedef.tmpl": "//blah"} - - // Input vars for code generation: - packageName := "api" - opts := Configuration{ - PackageName: packageName, - Generate: GenerateOptions{ - Models: true, - }, - OutputOptions: OutputOptions{ - UserTemplates: userTemplates, - }, - } - - // Get a spec from the example PetStore definition: - swagger, err := examplePetstore.GetSwagger() - assert.NoError(t, err) - - // Run our code generation: - code, err := Generate(swagger, opts) - assert.NoError(t, err) - assert.NotEmpty(t, code) - - // Check that we have valid (formattable) code: - _, err = format.Source([]byte(code)) - assert.NoError(t, err) - - // Check that we have a package: - assert.Contains(t, code, "package api") - - // Check that the built-in template has been overriden - assert.Contains(t, code, "//blah") -} - -func TestExamplePetStoreParseFunction(t *testing.T) { - - bodyBytes := []byte(`{"id": 5, "name": "testpet", "tag": "cat"}`) - - cannedResponse := &http.Response{ - StatusCode: 200, - Body: io.NopCloser(bytes.NewReader(bodyBytes)), - Header: http.Header{}, - } - cannedResponse.Header.Add("Content-type", "application/json") - - findPetByIDResponse, err := examplePetstoreClient.ParseFindPetByIDResponse(cannedResponse) - assert.NoError(t, err) - assert.NotNil(t, findPetByIDResponse.JSON200) - assert.Equal(t, int64(5), findPetByIDResponse.JSON200.Id) - assert.Equal(t, "testpet", findPetByIDResponse.JSON200.Name) - assert.NotNil(t, findPetByIDResponse.JSON200.Tag) - assert.Equal(t, "cat", *findPetByIDResponse.JSON200.Tag) -} - func TestExampleOpenAPICodeGeneration(t *testing.T) { // Input vars for code generation: @@ -145,8 +32,11 @@ func TestExampleOpenAPICodeGeneration(t *testing.T) { }, } + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + // Get a spec from the test definition in this file: - swagger, err := openapi3.NewLoader().LoadFromData([]byte(testOpenAPIDefinition)) + swagger, err := loader.LoadFromData([]byte(testOpenAPIDefinition)) assert.NoError(t, err) // Run our code generation: @@ -187,15 +77,57 @@ type GetTestByNameResponse struct { assert.Contains(t, code, "Top *int `form:\"$top,omitempty\" json:\"$top,omitempty\"`") assert.Contains(t, code, "func (c *Client) GetTestByName(ctx context.Context, name string, params *GetTestByNameParams, reqEditors ...RequestEditorFn) (*http.Response, error) {") assert.Contains(t, code, "func (c *ClientWithResponses) GetTestByNameWithResponse(ctx context.Context, name string, params *GetTestByNameParams, reqEditors ...RequestEditorFn) (*GetTestByNameResponse, error) {") - assert.Contains(t, code, "DeadSince *time.Time `json:\"dead_since,omitempty\" tag1:\"value1\" tag2:\"value2\"`") + assert.Contains(t, code, "FavouriteBirds *[]*string `json:\"favourite_birds,omitempty\"`") + assert.Contains(t, code, "DetestedBirds *[]string `json:\"detested_birds,omitempty\"`") + assert.Contains(t, code, "SlicedBirds []string `json:\"sliced_birds\"`") + assert.Contains(t, code, "ForgettableBirds *map[string]*string `json:\"forgettable_birds,omitempty\"`") + assert.Contains(t, code, "MemorableBirds *map[string]string `json:\"memorable_birds,omitempty\"`") + assert.Contains(t, code, "VeryMemorableBirds map[string]string `json:\"very_memorable_birds\"`") + assert.Contains(t, code, "DeadSince *time.Time `json:\"dead_since,omitempty\" tag1:\"value1\" tag2:\"value2\"`") + assert.Contains(t, code, "VeryDeadSince time.Time `json:\"very_dead_since\"`") assert.Contains(t, code, "type EnumTestNumerics int") assert.Contains(t, code, "N2 EnumTestNumerics = 2") assert.Contains(t, code, "type EnumTestEnumNames int") assert.Contains(t, code, "Two EnumTestEnumNames = 2") assert.Contains(t, code, "Double EnumTestEnumVarnames = 2") +} + +func TestExtPropGoTypeSkipOptionalPointer(t *testing.T) { + packageName := "api" + opts := Configuration{ + PackageName: packageName, + Generate: GenerateOptions{ + EchoServer: true, + Models: true, + EmbeddedSpec: true, + Strict: true, + }, + } + spec := "test_specs/x-go-type-skip-optional-pointer.yaml" + swagger, err := util.LoadSwagger(spec) + require.NoError(t, err) - // Make sure the generated code is valid: - checkLint(t, "test.gen.go", []byte(code)) + // Run our code generation: + code, err := Generate(swagger, opts) + assert.NoError(t, err) + assert.NotEmpty(t, code) + + // Check that we have valid (formattable) code: + _, err = format.Source([]byte(code)) + assert.NoError(t, err) + + // Check that optional pointer fields are skipped if requested + assert.Contains(t, code, "NullableFieldSkipFalse *string `json:\"nullableFieldSkipFalse,omitempty\"`") + assert.Contains(t, code, "NullableFieldSkipTrue string `json:\"nullableFieldSkipTrue,omitempty\"`") + assert.Contains(t, code, "OptionalField *string `json:\"optionalField,omitempty\"`") + assert.Contains(t, code, "OptionalFieldSkipFalse *string `json:\"optionalFieldSkipFalse,omitempty\"`") + assert.Contains(t, code, "OptionalFieldSkipTrue string `json:\"optionalFieldSkipTrue,omitempty\"`") + + // Check that the extension applies on custom types as well + assert.Contains(t, code, "CustomTypeWithSkipTrue string `json:\"customTypeWithSkipTrue,omitempty\"`") + + // Check that the extension has no effect on required fields + assert.Contains(t, code, "RequiredField string `json:\"requiredField\"`") } func TestGoTypeImport(t *testing.T) { @@ -239,13 +171,13 @@ func TestGoTypeImport(t *testing.T) { for _, imp := range imports { assert.Contains(t, code, imp) } - - // Make sure the generated code is valid: - checkLint(t, "test.gen.go", []byte(code)) - } func TestRemoteExternalReference(t *testing.T) { + if testing.Short() { + t.Skip("Skipping test that interacts with the network") + } + packageName := "api" opts := Configuration{ PackageName: packageName, @@ -273,7 +205,7 @@ func TestRemoteExternalReference(t *testing.T) { assert.Contains(t, code, "package api") // Check import - assert.Contains(t, code, `externalRef0 "github.com/deepmap/oapi-codegen/examples/petstore-expanded"`) + assert.Contains(t, code, `externalRef0 "github.com/oapi-codegen/oapi-codegen/v2/examples/petstore-expanded"`) // Check generated oneOf structure: assert.Contains(t, code, ` @@ -300,10 +232,66 @@ func (t *ExampleSchema_Item) FromExternalRef0NewPet(v externalRef0.NewPet) error // FromExternalRef0NewPet overwrites any union data inside the ExampleSchema_Item as the provided externalRef0.NewPet func (t *ExampleSchema_Item) FromExternalRef0NewPet(v externalRef0.NewPet) error { `) +} + +func TestDuplicatePathParameter(t *testing.T) { + // Regression test for https://github.com/oapi-codegen/oapi-codegen/issues/1574 + // Some real-world specs (e.g. Keycloak) have paths where the same parameter + // appears more than once: /clients/{client-uuid}/.../clients/{client-uuid} + spec := ` +openapi: "3.0.0" +info: + version: 1.0.0 + title: Duplicate path param test +paths: + /admin/realms/{realm}/clients/{client-uuid}/roles/{role-name}/composites/clients/{client-uuid}: + get: + operationId: getCompositeRoles + parameters: + - name: realm + in: path + required: true + schema: + type: string + - name: client-uuid + in: path + required: true + schema: + type: string + - name: role-name + in: path + required: true + schema: + type: string + responses: + '200': + description: Success +` + loader := openapi3.NewLoader() + swagger, err := loader.LoadFromData([]byte(spec)) + require.NoError(t, err) + + opts := Configuration{ + PackageName: "api", + Generate: GenerateOptions{ + EchoServer: true, + Client: true, + Models: true, + }, + } - // Make sure the generated code is valid: - checkLint(t, "test.gen.go", []byte(code)) + code, err := Generate(swagger, opts) + require.NoError(t, err) + assert.NotEmpty(t, code) + + // Verify the generated code is valid Go. + _, err = format.Source([]byte(code)) + require.NoError(t, err) + // The path params should appear exactly once in the function signature. + assert.Contains(t, code, "realm string") + assert.Contains(t, code, "clientUuid string") + assert.Contains(t, code, "roleName string") } //go:embed test_spec.yaml diff --git a/pkg/codegen/configuration.go b/pkg/codegen/configuration.go index afa4fc5020..0f35d13a90 100644 --- a/pkg/codegen/configuration.go +++ b/pkg/codegen/configuration.go @@ -2,6 +2,7 @@ package codegen import ( "errors" + "fmt" "reflect" ) @@ -12,24 +13,207 @@ type AdditionalImport struct { // Configuration defines code generation customizations type Configuration struct { - PackageName string `yaml:"package"` // PackageName to generate - Generate GenerateOptions `yaml:"generate,omitempty"` - Compatibility CompatibilityOptions `yaml:"compatibility,omitempty"` - OutputOptions OutputOptions `yaml:"output-options,omitempty"` - ImportMapping map[string]string `yaml:"import-mapping,omitempty"` // ImportMapping specifies the golang package path for each external reference - AdditionalImports []AdditionalImport `yaml:"additional-imports,omitempty"` + // PackageName to generate the code under + PackageName string `yaml:"package"` + // Generate specifies which supported output formats to generate + Generate GenerateOptions `yaml:"generate,omitempty"` + // CompatibilityOptions specifies backward compatibility settings for the code generator + Compatibility CompatibilityOptions `yaml:"compatibility,omitempty"` + // OutputOptions are used to modify the output code in some way. + OutputOptions OutputOptions `yaml:"output-options,omitempty"` + // ImportMapping specifies the golang package path for each external reference + ImportMapping map[string]string `yaml:"import-mapping,omitempty"` + // AdditionalImports defines any additional Go imports to add to the generated code + AdditionalImports []AdditionalImport `yaml:"additional-imports,omitempty"` + // NoVCSVersionOverride allows overriding the version of the application for cases where no Version Control System (VCS) is available when building, for instance when using a Nix derivation. + // See documentation for how to use it in examples/no-vcs-version-override/README.md + NoVCSVersionOverride *string `yaml:"-"` +} + +// Validate checks whether Configuration represent a valid configuration +func (o Configuration) Validate() error { + if o.PackageName == "" { + return errors.New("package name must be specified") + } + + // Only one server type should be specified at a time. + nServers := 0 + if o.Generate.IrisServer { + nServers++ + } + if o.Generate.ChiServer { + nServers++ + } + if o.Generate.FiberServer { + nServers++ + } + if o.Generate.EchoServer { + nServers++ + } + if o.Generate.Echo5Server { + nServers++ + } + if o.Generate.GorillaServer { + nServers++ + } + if o.Generate.StdHTTPServer { + nServers++ + } + if o.Generate.GinServer { + nServers++ + } + if nServers > 1 { + return errors.New("only one server type is supported at a time") + } + + var errs []error + if problems := o.Generate.Validate(); problems != nil { + for k, v := range problems { + errs = append(errs, fmt.Errorf("`generate` configuration for %v was incorrect: %v", k, v)) + } + } + + if problems := o.Compatibility.Validate(); problems != nil { + for k, v := range problems { + errs = append(errs, fmt.Errorf("`compatibility-options` configuration for %v was incorrect: %v", k, v)) + } + } + + if problems := o.OutputOptions.Validate(); problems != nil { + for k, v := range problems { + errs = append(errs, fmt.Errorf("`output-options` configuration for %v was incorrect: %v", k, v)) + } + } + + err := errors.Join(errs...) + if err != nil { + return fmt.Errorf("failed to validate configuration: %w", err) + } + + return nil +} + +// UpdateDefaults sets reasonable default values for unset fields in Configuration +func (o Configuration) UpdateDefaults() Configuration { + if reflect.ValueOf(o.Generate).IsZero() { + o.Generate = GenerateOptions{ + EchoServer: true, + Models: true, + EmbeddedSpec: true, + } + } + return o } // GenerateOptions specifies which supported output formats to generate. type GenerateOptions struct { - ChiServer bool `yaml:"chi-server,omitempty"` // ChiServer specifies whether to generate chi server boilerplate - EchoServer bool `yaml:"echo-server,omitempty"` // EchoServer specifies whether to generate echo server boilerplate - GinServer bool `yaml:"gin-server,omitempty"` // GinServer specifies whether to generate gin server boilerplate - GorillaServer bool `yaml:"gorilla-server,omitempty"` // GorillaServer specifies whether to generate Gorilla server boilerplate - Strict bool `yaml:"strict-server,omitempty"` // Strict specifies whether to generate strict server wrapper - Client bool `yaml:"client,omitempty"` // Client specifies whether to generate client boilerplate - Models bool `yaml:"models,omitempty"` // Models specifies whether to generate type definitions - EmbeddedSpec bool `yaml:"embedded-spec,omitempty"` // Whether to embed the swagger spec in the generated code + // IrisServer specifies whether to generate iris server boilerplate + IrisServer bool `yaml:"iris-server,omitempty"` + // ChiServer specifies whether to generate chi server boilerplate + ChiServer bool `yaml:"chi-server,omitempty"` + // FiberServer specifies whether to generate fiber server boilerplate + FiberServer bool `yaml:"fiber-server,omitempty"` + // EchoServer specifies whether to generate echo server boilerplate + EchoServer bool `yaml:"echo-server,omitempty"` + // Echo5Server specifies whether to generate echo v5 server boilerplate + Echo5Server bool `yaml:"echo5-server,omitempty"` + // GinServer specifies whether to generate gin server boilerplate + GinServer bool `yaml:"gin-server,omitempty"` + // GorillaServer specifies whether to generate Gorilla server boilerplate + GorillaServer bool `yaml:"gorilla-server,omitempty"` + // StdHTTPServer specifies whether to generate stdlib http server boilerplate + StdHTTPServer bool `yaml:"std-http-server,omitempty"` + // Strict specifies whether to generate strict server wrapper + Strict bool `yaml:"strict-server,omitempty"` + // Client specifies whether to generate client boilerplate + Client bool `yaml:"client,omitempty"` + // Models specifies whether to generate type definitions + Models bool `yaml:"models,omitempty"` + // EmbeddedSpec indicates whether to embed the swagger spec in the generated code + EmbeddedSpec bool `yaml:"embedded-spec,omitempty"` + // ServerURLs generates types for the `Server` definitions' URLs, instead of needing to provide your own values + ServerURLs bool `yaml:"server-urls,omitempty"` +} + +// RouterImports returns the framework-specific and strict middleware imports +// needed based on which server type is selected. +func (g GenerateOptions) RouterImports() []AdditionalImport { + var imports []AdditionalImport + + switch { + case g.EchoServer: + imports = append(imports, AdditionalImport{Package: "github.com/labstack/echo/v4"}) + if g.Strict { + imports = append(imports, AdditionalImport{Alias: "strictecho", Package: "github.com/oapi-codegen/runtime/strictmiddleware/echo"}) + } + case g.Echo5Server: + imports = append(imports, AdditionalImport{Package: "github.com/labstack/echo/v5"}) + if g.Strict { + imports = append(imports, AdditionalImport{Alias: "strictecho5", Package: "github.com/oapi-codegen/runtime/strictmiddleware/echo/v5"}) + } + case g.ChiServer: + imports = append(imports, AdditionalImport{Package: "github.com/go-chi/chi/v5"}) + if g.Strict { + imports = append(imports, AdditionalImport{Alias: "strictnethttp", Package: "github.com/oapi-codegen/runtime/strictmiddleware/nethttp"}) + } + case g.GinServer: + imports = append(imports, AdditionalImport{Package: "github.com/gin-gonic/gin"}) + if g.Strict { + imports = append(imports, AdditionalImport{Alias: "strictgin", Package: "github.com/oapi-codegen/runtime/strictmiddleware/gin"}) + } + case g.GorillaServer: + imports = append(imports, AdditionalImport{Package: "github.com/gorilla/mux"}) + if g.Strict { + imports = append(imports, AdditionalImport{Alias: "strictnethttp", Package: "github.com/oapi-codegen/runtime/strictmiddleware/nethttp"}) + } + case g.FiberServer: + imports = append(imports, AdditionalImport{Package: "github.com/gofiber/fiber/v2"}) + case g.IrisServer: + imports = append(imports, AdditionalImport{Package: "github.com/kataras/iris/v12"}) + imports = append(imports, AdditionalImport{Package: "github.com/kataras/iris/v12/core/router"}) + if g.Strict { + imports = append(imports, AdditionalImport{Alias: "strictiris", Package: "github.com/oapi-codegen/runtime/strictmiddleware/iris"}) + } + case g.StdHTTPServer: + if g.Strict { + imports = append(imports, AdditionalImport{Alias: "strictnethttp", Package: "github.com/oapi-codegen/runtime/strictmiddleware/nethttp"}) + } + } + + return imports +} + +func (oo GenerateOptions) Validate() map[string]string { + return nil +} + +func (oo GenerateOptions) Warnings() map[string]string { + warnings := make(map[string]string) + + if oo.StdHTTPServer { + if warning := oo.warningForStdHTTP(); warning != "" { + warnings["std-http-server"] = warning + } + } + + return warnings +} + +func (oo GenerateOptions) warningForStdHTTP() string { + pathToGoMod, mod, err := findAndParseGoModuleForDepth(".", maximumDepthToSearchForGoMod) + if err != nil { + return fmt.Sprintf("Encountered an error while trying to find a `go.mod` or a `tools.mod` in this directory, or %d levels above it: %v", maximumDepthToSearchForGoMod, err) + } + + if mod == nil { + return fmt.Sprintf("Failed to find a `go.mod` or a `tools.mod` in this directory, or %d levels above it, so unable to validate that you're using Go 1.22+. If you start seeing API interactions resulting in a `404 page not found`, the Go directive (implying source compatibility for this module) needs to be bumped. See also: https://www.jvt.me/posts/2024/03/04/go-net-http-why-404/", maximumDepthToSearchForGoMod) + } + + if !hasMinimalMinorGoDirective(minimumGoVersionForGenerateStdHTTPServer, mod) { + return fmt.Sprintf("Found a `go.mod` or a `tools.mod` at path %v, but it only had a version of %v, whereas the minimum required is 1.%d. It's very likely API interactions will result in a `404 page not found`. The Go directive (implying source compatibility for this module) needs to be bumped. See also: https://www.jvt.me/posts/2024/03/04/go-net-http-why-404/", pathToGoMod, mod.Go.Version, minimumGoVersionForGenerateStdHTTPServer) + } + + return "" } // CompatibilityOptions specifies backward compatibility settings for the @@ -40,26 +224,26 @@ type CompatibilityOptions struct { // `allOf` merges at the schema definition level, not at the resulting model // level. So, new behavior merges OpenAPI specs but generates different code // than we have in the past. Set OldMergeSchemas to true for the old behavior. - // Please see https://github.com/deepmap/oapi-codegen/issues/531 + // Please see https://github.com/oapi-codegen/oapi-codegen/issues/531 OldMergeSchemas bool `yaml:"old-merge-schemas,omitempty"` // Enum values can generate conflicting typenames, so we've updated the // code for enum generation to avoid these conflicts, but it will result // in some enum types being renamed in existing code. Set OldEnumConflicts to true // to revert to old behavior. Please see: - // Please see https://github.com/deepmap/oapi-codegen/issues/549 + // Please see https://github.com/oapi-codegen/oapi-codegen/issues/549 OldEnumConflicts bool `yaml:"old-enum-conflicts,omitempty"` // It was a mistake to generate a go type definition for every $ref in // the OpenAPI schema. New behavior uses type aliases where possible, but // this can generate code which breaks existing builds. Set OldAliasing to true // for old behavior. - // Please see https://github.com/deepmap/oapi-codegen/issues/549 + // Please see https://github.com/oapi-codegen/oapi-codegen/issues/549 OldAliasing bool `yaml:"old-aliasing,omitempty"` // When an object contains no members, and only an additionalProperties specification, - // it is flattened to a map. Set + // it is flattened to a map DisableFlattenAdditionalProperties bool `yaml:"disable-flatten-additional-properties,omitempty"` // When an object property is both required and readOnly the go model is generated // as a pointer. Set DisableRequiredReadOnlyAsPointer to true to mark them as non pointer. - // Please see https://github.com/deepmap/oapi-codegen/issues/604 + // Please see https://github.com/oapi-codegen/oapi-codegen/issues/604 DisableRequiredReadOnlyAsPointer bool `yaml:"disable-required-readonly-as-pointer,omitempty"` // When set to true, always prefix enum values with their type name instead of only // when typenames would be conflicting. @@ -67,59 +251,138 @@ type CompatibilityOptions struct { // Our generated code for Chi has historically inverted the order in which Chi middleware is // applied such that the last invoked middleware ends up executing first in the Chi chain // This resolves the behavior such that middlewares are chained in the order they are invoked. - // Please see https://github.com/deepmap/oapi-codegen/issues/786 + // Please see https://github.com/oapi-codegen/oapi-codegen/issues/786 ApplyChiMiddlewareFirstToLast bool `yaml:"apply-chi-middleware-first-to-last,omitempty"` // Our generated code for gorilla/mux has historically inverted the order in which gorilla/mux middleware is // applied such that the last invoked middleware ends up executing first in the middlewares chain // This resolves the behavior such that middlewares are chained in the order they are invoked. - // Please see https://github.com/deepmap/oapi-codegen/issues/841 + // Please see https://github.com/oapi-codegen/oapi-codegen/issues/841 ApplyGorillaMiddlewareFirstToLast bool `yaml:"apply-gorilla-middleware-first-to-last,omitempty"` + // CircularReferenceLimit allows controlling the limit for circular reference checking. + // In some OpenAPI specifications, we have a higher number of circular + // references than is allowed out-of-the-box, but can be tuned to allow + // traversing them. + // Deprecated: In kin-openapi v0.126.0 (https://github.com/getkin/kin-openapi/tree/v0.126.0?tab=readme-ov-file#v01260) the Circular Reference Counter functionality was removed, instead resolving all references with backtracking, to avoid needing to provide a limit to reference counts. + CircularReferenceLimit int `yaml:"circular-reference-limit"` + // AllowUnexportedStructFieldNames makes it possible to output structs that have fields that are unexported. + // + // This is expected to be used in conjunction with `x-go-name` and `x-oapi-codegen-only-honour-go-name` to override the resulting output field name, and `x-oapi-codegen-extra-tags` to not produce JSON tags for `encoding/json`, such as: + // + // ```yaml + // id: + // type: string + // x-go-name: accountIdentifier + // x-oapi-codegen-extra-tags: + // json: "-" + // x-oapi-codegen-only-honour-go-name: true + // ``` + // + // NOTE that this can be confusing to users of your OpenAPI specification, who may see a field present and therefore be expecting to see/use it in the request/response, without understanding the nuance of how `oapi-codegen` generates the code. + AllowUnexportedStructFieldNames bool `yaml:"allow-unexported-struct-field-names"` + + // PreserveOriginalOperationIdCasingInEmbeddedSpec ensures that the `operationId` from the source spec is kept intact in case when embedding it into the Embedded Spec output. + // When `oapi-codegen` parses the original OpenAPI specification, it will apply the configured `output-options.name-normalizer` to each operation's `operationId` before that is used to generate code from. + // However, this is also applied to the copy of the `operationId`s in the `embedded-spec` generation, which means that the embedded OpenAPI specification is then out-of-sync with the input specificiation. + // To ensure that the `operationId` in the embedded spec is preserved as-is from the input specification, set this. + // NOTE that this will not impact generated code. + // NOTE that if you're using `include-operation-ids` or `exclude-operation-ids` you may want to ensure that the `operationId`s used are correct. + PreserveOriginalOperationIdCasingInEmbeddedSpec bool `yaml:"preserve-original-operation-id-casing-in-embedded-spec"` +} + +func (co CompatibilityOptions) Validate() map[string]string { + return nil } // OutputOptions are used to modify the output code in some way. type OutputOptions struct { - SkipFmt bool `yaml:"skip-fmt,omitempty"` // Whether to skip go imports on the generated code - SkipPrune bool `yaml:"skip-prune,omitempty"` // Whether to skip pruning unused components on the generated code - IncludeTags []string `yaml:"include-tags,omitempty"` // Only include operations that have one of these tags. Ignored when empty. - ExcludeTags []string `yaml:"exclude-tags,omitempty"` // Exclude operations that have one of these tags. Ignored when empty. - UserTemplates map[string]string `yaml:"user-templates,omitempty"` // Override built-in templates from user-provided files - - ExcludeSchemas []string `yaml:"exclude-schemas,omitempty"` // Exclude from generation schemas with given names. Ignored when empty. - ResponseTypeSuffix string `yaml:"response-type-suffix,omitempty"` // The suffix used for responses types - ClientTypeName string `yaml:"client-type-name,omitempty"` // Override the default generated client type with the value + // Whether to skip go imports on the generated code + SkipFmt bool `yaml:"skip-fmt,omitempty"` + // Whether to skip pruning unused components on the generated code + SkipPrune bool `yaml:"skip-prune,omitempty"` + // Only include operations that have one of these tags. Ignored when empty. + IncludeTags []string `yaml:"include-tags,omitempty"` + // Exclude operations that have one of these tags. Ignored when empty. + ExcludeTags []string `yaml:"exclude-tags,omitempty"` + // Only include operations that have one of these operation-ids. Ignored when empty. + IncludeOperationIDs []string `yaml:"include-operation-ids,omitempty"` + // Exclude operations that have one of these operation-ids. Ignored when empty. + ExcludeOperationIDs []string `yaml:"exclude-operation-ids,omitempty"` + // Override built-in templates from user-provided files + UserTemplates map[string]string `yaml:"user-templates,omitempty"` + + // Exclude from generation schemas with given names. Ignored when empty. + ExcludeSchemas []string `yaml:"exclude-schemas,omitempty"` + // The suffix used for responses types + ResponseTypeSuffix string `yaml:"response-type-suffix,omitempty"` + // Override the default generated client type with the value + ClientTypeName string `yaml:"client-type-name,omitempty"` + // Whether to use the initialism overrides + InitialismOverrides bool `yaml:"initialism-overrides,omitempty"` + // AdditionalInitialisms is a list of additional initialisms to use when generating names. + // NOTE that this has no effect unless the `name-normalizer` is set to `ToCamelCaseWithInitialisms` + AdditionalInitialisms []string `yaml:"additional-initialisms,omitempty"` + // Whether to generate nullable type for nullable fields + NullableType bool `yaml:"nullable-type,omitempty"` + + // DisableTypeAliasesForType allows defining which OpenAPI `type`s will explicitly not use type aliases + // Currently supports: + // "array" + DisableTypeAliasesForType []string `yaml:"disable-type-aliases-for-type"` + + // NameNormalizer is the method used to normalize Go names and types, for instance converting the text `MyApi` to `MyAPI`. Corresponds with the constants defined for `codegen.NameNormalizerFunction` + NameNormalizer string `yaml:"name-normalizer,omitempty"` + + // Overlay defines configuration for the OpenAPI Overlay (https://github.com/OAI/Overlay-Specification) to manipulate the OpenAPI specification before generation. This allows modifying the specification without needing to apply changes directly to it, making it easier to keep it up-to-date. + Overlay OutputOptionsOverlay `yaml:"overlay"` + + // EnableYamlTags adds YAML tags to generated structs, in addition to default JSON ones + EnableYamlTags bool `yaml:"yaml-tags,omitempty"` + + // ClientResponseBytesFunction decides whether to enable the generation of a `Bytes()` method on response objects for `ClientWithResponses` + ClientResponseBytesFunction bool `yaml:"client-response-bytes-function,omitempty"` + + // PreferSkipOptionalPointer allows defining at a global level whether to omit the pointer for a type to indicate that the field/type is optional. + // This is the same as adding `x-go-type-skip-optional-pointer` to each field (manually, or using an OpenAPI Overlay) + PreferSkipOptionalPointer bool `yaml:"prefer-skip-optional-pointer,omitempty"` + + // PreferSkipOptionalPointerWithOmitzero allows generating the `omitzero` JSON tag types that would have had an optional pointer. + // This is the same as adding `x-omitzero` to each field (manually, or using an OpenAPI Overlay). + // A field can set `x-omitzero: false` to disable the `omitzero` JSON tag. + // NOTE that this must be used alongside `prefer-skip-optional-pointer`, otherwise makes no difference. + PreferSkipOptionalPointerWithOmitzero bool `yaml:"prefer-skip-optional-pointer-with-omitzero,omitempty"` + + // PreferSkipOptionalPointerOnContainerTypes allows disabling the generation of an "optional pointer" for an optional field that is a container type (such as a slice or a map), which ends up requiring an additional, unnecessary, `... != nil` check + PreferSkipOptionalPointerOnContainerTypes bool `yaml:"prefer-skip-optional-pointer-on-container-types,omitempty"` + + // ResolveTypeNameCollisions, when set to true, automatically renames + // types that collide across different OpenAPI component sections + // (schemas, parameters, requestBodies, responses, headers) by appending + // a suffix based on the component section (e.g., "Parameter", "Response", + // "RequestBody"). It also detects collisions between component types and + // client response wrapper types (e.g., issue #1474). Without this, the + // codegen will error on duplicate type names, requiring manual resolution + // via x-go-name. + ResolveTypeNameCollisions bool `yaml:"resolve-type-name-collisions,omitempty"` + + // TypeMapping allows customizing OpenAPI type/format to Go type mappings. + // User-specified mappings are merged on top of the defaults. + TypeMapping *TypeMapping `yaml:"type-mapping,omitempty"` } -// UpdateDefaults sets reasonable default values for unset fields in Configuration -func (o Configuration) UpdateDefaults() Configuration { - if reflect.ValueOf(o.Generate).IsZero() { - o.Generate = GenerateOptions{ - EchoServer: true, - Models: true, - EmbeddedSpec: true, +func (oo OutputOptions) Validate() map[string]string { + if NameNormalizerFunction(oo.NameNormalizer) != NameNormalizerFunctionToCamelCaseWithInitialisms && len(oo.AdditionalInitialisms) > 0 { + return map[string]string{ + "additional-initialisms": "You have specified `additional-initialisms`, but the `name-normalizer` is not set to `ToCamelCaseWithInitialisms`. Please specify `name-normalizer: ToCamelCaseWithInitialisms` or remove the `additional-initialisms` configuration", } } - return o + + return nil } -// Validate checks whether Configuration represent a valid configuration -func (o Configuration) Validate() error { - if o.PackageName == "" { - return errors.New("package name must be specified") - } +type OutputOptionsOverlay struct { + Path string `yaml:"path"` - // Only one server type should be specified at a time. - nServers := 0 - if o.Generate.ChiServer { - nServers++ - } - if o.Generate.EchoServer { - nServers++ - } - if o.Generate.GinServer { - nServers++ - } - if nServers > 1 { - return errors.New("only one server type is supported at a time") - } - return nil + // Strict defines whether the Overlay should be applied in a strict way, highlighting any actions that will not take any effect. This can, however, lead to more work when testing new actions in an Overlay, so can be turned off with this setting. + // Defaults to true. + Strict *bool `yaml:"strict,omitempty"` } diff --git a/pkg/codegen/extension.go b/pkg/codegen/extension.go index 483d6d8142..dbf6611f4d 100644 --- a/pkg/codegen/extension.go +++ b/pkg/codegen/extension.go @@ -5,37 +5,61 @@ import ( ) const ( - // extPropGoType overrides the generated type definition. + // extPropGoType overrides the generated type definition. When + // resolve-type-name-collisions is enabled, the collision resolver + // controls the final Go type name; this extension controls what + // that name aliases or refers to. extPropGoType = "x-go-type" + // extPropGoTypeSkipOptionalPointer specifies that optional fields should + // be the type itself instead of a pointer to the type. + extPropGoTypeSkipOptionalPointer = "x-go-type-skip-optional-pointer" // extPropGoImport specifies the module to import which provides above type extPropGoImport = "x-go-type-import" // extGoName is used to override a field name extGoName = "x-go-name" - // extGoTypeName is used to override a generated typename for something. - extGoTypeName = "x-go-type-name" - extPropGoJsonIgnore = "x-go-json-ignore" - extPropOmitEmpty = "x-omitempty" - extPropExtraTags = "x-oapi-codegen-extra-tags" - extEnumVarNames = "x-enum-varnames" - extEnumNames = "x-enumNames" + // extGoTypeName overrides a generated typename. When + // resolve-type-name-collisions is enabled, the collision resolver + // controls the top-level Go type name; this extension controls + // the name of the underlying type definition that gets aliased. + extGoTypeName = "x-go-type-name" + extPropGoJsonIgnore = "x-go-json-ignore" + extPropOmitEmpty = "x-omitempty" + extPropOmitZero = "x-omitzero" + extPropExtraTags = "x-oapi-codegen-extra-tags" + extEnumVarNames = "x-enum-varnames" + extEnumNames = "x-enumNames" + extDeprecationReason = "x-deprecated-reason" + extOrder = "x-order" + // extOapiCodegenOnlyHonourGoName is to be used to explicitly enforce the generation of a field as the `x-go-name` extension has describe it. + // This is intended to be used alongside the `allow-unexported-struct-field-names` Compatibility option + extOapiCodegenOnlyHonourGoName = "x-oapi-codegen-only-honour-go-name" ) -func extString(extPropValue interface{}) (string, error) { +func extString(extPropValue any) (string, error) { str, ok := extPropValue.(string) if !ok { return "", fmt.Errorf("failed to convert type: %T", extPropValue) } return str, nil } -func extTypeName(extPropValue interface{}) (string, error) { + +func extTypeName(extPropValue any) (string, error) { return extString(extPropValue) } -func extParseGoFieldName(extPropValue interface{}) (string, error) { +func extParsePropGoTypeSkipOptionalPointer(extPropValue any) (bool, error) { + goTypeSkipOptionalPointer, ok := extPropValue.(bool) + if !ok { + return false, fmt.Errorf("failed to convert type: %T", extPropValue) + } + return goTypeSkipOptionalPointer, nil +} + +func extParseGoFieldName(extPropValue any) (string, error) { return extString(extPropValue) } -func extParseOmitEmpty(extPropValue interface{}) (bool, error) { +func extParseOmitEmpty(extPropValue any) (bool, error) { omitEmpty, ok := extPropValue.(bool) if !ok { return false, fmt.Errorf("failed to convert type: %T", extPropValue) @@ -43,8 +67,16 @@ func extParseOmitEmpty(extPropValue interface{}) (bool, error) { return omitEmpty, nil } -func extExtraTags(extPropValue interface{}) (map[string]string, error) { - tagsI, ok := extPropValue.(map[string]interface{}) +func extParseOmitZero(extPropValue any) (bool, error) { + omitZero, ok := extPropValue.(bool) + if !ok { + return false, fmt.Errorf("failed to convert type: %T", extPropValue) + } + return omitZero, nil +} + +func extExtraTags(extPropValue any) (map[string]string, error) { + tagsI, ok := extPropValue.(map[string]any) if !ok { return nil, fmt.Errorf("failed to convert type: %T", extPropValue) } @@ -59,7 +91,7 @@ func extExtraTags(extPropValue interface{}) (map[string]string, error) { return tags, nil } -func extParseGoJsonIgnore(extPropValue interface{}) (bool, error) { +func extParseGoJsonIgnore(extPropValue any) (bool, error) { goJsonIgnore, ok := extPropValue.(bool) if !ok { return false, fmt.Errorf("failed to convert type: %T", extPropValue) @@ -67,8 +99,8 @@ func extParseGoJsonIgnore(extPropValue interface{}) (bool, error) { return goJsonIgnore, nil } -func extParseEnumVarNames(extPropValue interface{}) ([]string, error) { - namesI, ok := extPropValue.([]interface{}) +func extParseEnumVarNames(extPropValue any) ([]string, error) { + namesI, ok := extPropValue.([]any) if !ok { return nil, fmt.Errorf("failed to convert type: %T", extPropValue) } @@ -82,3 +114,15 @@ func extParseEnumVarNames(extPropValue interface{}) ([]string, error) { } return names, nil } + +func extParseDeprecationReason(extPropValue any) (string, error) { + return extString(extPropValue) +} + +func extParseOapiCodegenOnlyHonourGoName(extPropValue any) (bool, error) { + onlyHonourGoName, ok := extPropValue.(bool) + if !ok { + return false, fmt.Errorf("failed to convert type: %T", extPropValue) + } + return onlyHonourGoName, nil +} diff --git a/pkg/codegen/extension_test.go b/pkg/codegen/extension_test.go index 1d5a215639..0223ee9404 100644 --- a/pkg/codegen/extension_test.go +++ b/pkg/codegen/extension_test.go @@ -39,7 +39,7 @@ func Test_extTypeName(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // kin-openapi no longer returns these as RawMessage - var extPropValue interface{} + var extPropValue any if tt.args.extPropValue != nil { err := json.Unmarshal(tt.args.extPropValue, &extPropValue) assert.NoError(t, err) @@ -54,3 +54,57 @@ func Test_extTypeName(t *testing.T) { }) } } + +func Test_extParsePropGoTypeSkipOptionalPointer(t *testing.T) { + type args struct { + extPropValue json.RawMessage + } + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + { + name: "success when set to true", + args: args{json.RawMessage(`true`)}, + want: true, + wantErr: false, + }, + { + name: "success when set to false", + args: args{json.RawMessage(`false`)}, + want: false, + wantErr: false, + }, + { + name: "nil conversion error", + args: args{nil}, + want: false, + wantErr: true, + }, + { + name: "type conversion error", + args: args{json.RawMessage(`"true"`)}, + want: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // kin-openapi no longer returns these as RawMessage + var extPropValue any + if tt.args.extPropValue != nil { + err := json.Unmarshal(tt.args.extPropValue, &extPropValue) + assert.NoError(t, err) + } + got, err := extParsePropGoTypeSkipOptionalPointer(extPropValue) + if tt.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/codegen/externalref.go b/pkg/codegen/externalref.go new file mode 100644 index 0000000000..a48b3d4e1e --- /dev/null +++ b/pkg/codegen/externalref.go @@ -0,0 +1,80 @@ +package codegen + +import ( + "fmt" + "strings" +) + +// ensureExternalRefsInRequestBodyDefinitions ensures that when an externalRef (`$ref` that points to a file that isn't the current spec) is encountered, we make sure we update our underlying `RefType` to make sure that we point to that type. +// This only happens if we have a non-empty `ref` passed in, and that `ref` isn't pointing to something in our file +// NOTE that the pointer here allows us to pass in a reference and edit in-place +func ensureExternalRefsInRequestBodyDefinitions(defs *[]RequestBodyDefinition, ref string) { + if ref == "" { + return + } + + for i, rbd := range *defs { + ensureExternalRefsInSchema(&rbd.Schema, ref) + + // make sure we then update it in-place + (*defs)[i] = rbd + } +} + +// ensureExternalRefsInResponseDefinitions ensures that when an externalRef (`$ref` that points to a file that isn't the current spec) is encountered, we make sure we update our underlying `RefType` to make sure that we point to that type. +// This only happens if we have a non-empty `ref` passed in, and that `ref` isn't pointing to something in our file +// NOTE that the pointer here allows us to pass in a reference and edit in-place +func ensureExternalRefsInResponseDefinitions(defs *[]ResponseDefinition, ref string) { + if ref == "" { + return + } + + for i, rd := range *defs { + for j, rcd := range rd.Contents { + ensureExternalRefsInSchema(&rcd.Schema, ref) + + // make sure we then update it in-place + rd.Contents[j] = rcd + } + + // make sure we then update it in-place + (*defs)[i] = rd + } +} + +// ensureExternalRefsInParameterDefinitions ensures that when an externalRef (`$ref` that points to a file that isn't the current spec) is encountered, we make sure we update our underlying `RefType` to make sure that we point to that type. +// This only happens if we have a non-empty `ref` passed in, and that `ref` isn't pointing to something in our file +// NOTE that the pointer here allows us to pass in a reference and edit in-place +func ensureExternalRefsInParameterDefinitions(defs *[]ParameterDefinition, ref string) { + if ref == "" { + return + } + + for i, pd := range *defs { + ensureExternalRefsInSchema(&pd.Schema, ref) + + // make sure we then update it in-place + (*defs)[i] = pd + } +} + +// ensureExternalRefsInSchema ensures that when an externalRef (`$ref` that points to a file that isn't the current spec) is encountered, we make sure we update our underlying `RefType` to make sure that we point to that type. +// +// This only happens if we have a non-empty `ref` passed in, and that `ref` isn't pointing to something in our file +// +// NOTE that the pointer here allows us to pass in a reference and edit in-place +func ensureExternalRefsInSchema(schema *Schema, ref string) { + if ref == "" { + return + } + + // if this is already defined as the start of a struct, we shouldn't inject **??** + if strings.HasPrefix(schema.GoType, "struct {") { + return + } + + parts := strings.SplitN(ref, "#", 2) + if pack, ok := globalState.importMapping[parts[0]]; ok { + schema.RefType = fmt.Sprintf("%s.%s", pack.Name, schema.GoType) + } +} diff --git a/pkg/codegen/filter.go b/pkg/codegen/filter.go index 01a3639575..435968a379 100644 --- a/pkg/codegen/filter.go +++ b/pkg/codegen/filter.go @@ -2,21 +2,29 @@ package codegen import "github.com/getkin/kin-openapi/openapi3" +func sliceToMap(items []string) map[string]bool { + m := make(map[string]bool, len(items)) + for _, item := range items { + m[item] = true + } + return m +} + func filterOperationsByTag(swagger *openapi3.T, opts Configuration) { if len(opts.OutputOptions.ExcludeTags) > 0 { - excludeOperationsWithTags(swagger.Paths, opts.OutputOptions.ExcludeTags) + operationsWithTags(swagger.Paths, sliceToMap(opts.OutputOptions.ExcludeTags), true) } if len(opts.OutputOptions.IncludeTags) > 0 { - includeOperationsWithTags(swagger.Paths, opts.OutputOptions.IncludeTags, false) + operationsWithTags(swagger.Paths, sliceToMap(opts.OutputOptions.IncludeTags), false) } } -func excludeOperationsWithTags(paths openapi3.Paths, tags []string) { - includeOperationsWithTags(paths, tags, true) -} +func operationsWithTags(paths *openapi3.Paths, tags map[string]bool, exclude bool) { + if paths == nil { + return + } -func includeOperationsWithTags(paths openapi3.Paths, tags []string, exclude bool) { - for _, pathItem := range paths { + for _, pathItem := range paths.Map() { ops := pathItem.Operations() names := make([]string, 0, len(ops)) for name, op := range ops { @@ -31,16 +39,50 @@ func includeOperationsWithTags(paths openapi3.Paths, tags []string, exclude bool } // operationHasTag returns true if the operation is tagged with any of tags -func operationHasTag(op *openapi3.Operation, tags []string) bool { +func operationHasTag(op *openapi3.Operation, tags map[string]bool) bool { if op == nil { return false } for _, hasTag := range op.Tags { - for _, wantTag := range tags { - if hasTag == wantTag { - return true - } + if tags[hasTag] { + return true } } return false } + +func filterOperationsByOperationID(swagger *openapi3.T, opts Configuration) { + if len(opts.OutputOptions.ExcludeOperationIDs) > 0 { + operationsWithOperationIDs(swagger.Paths, sliceToMap(opts.OutputOptions.ExcludeOperationIDs), true) + } + if len(opts.OutputOptions.IncludeOperationIDs) > 0 { + operationsWithOperationIDs(swagger.Paths, sliceToMap(opts.OutputOptions.IncludeOperationIDs), false) + } +} + +func operationsWithOperationIDs(paths *openapi3.Paths, operationIDs map[string]bool, exclude bool) { + if paths == nil { + return + } + + for _, pathItem := range paths.Map() { + ops := pathItem.Operations() + names := make([]string, 0, len(ops)) + for name, op := range ops { + if operationHasOperationID(op, operationIDs) == exclude { + names = append(names, name) + } + } + for _, name := range names { + pathItem.SetOperation(name, nil) + } + } +} + +// operationHasOperationID returns true if the operation has operation id is included in operation ids +func operationHasOperationID(op *openapi3.Operation, operationIDs map[string]bool) bool { + if op == nil { + return false + } + return operationIDs[op.OperationID] +} diff --git a/pkg/codegen/filter_test.go b/pkg/codegen/filter_test.go index d3a5aec546..09dcf5c5a3 100644 --- a/pkg/codegen/filter_test.go +++ b/pkg/codegen/filter_test.go @@ -23,8 +23,11 @@ func TestFilterOperationsByTag(t *testing.T) { }, } + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + // Get a spec from the test definition in this file: - swagger, err := openapi3.NewLoader().LoadFromData([]byte(testOpenAPIDefinition)) + swagger, err := loader.LoadFromData([]byte(testOpenAPIDefinition)) assert.NoError(t, err) // Run our code generation: @@ -49,8 +52,72 @@ func TestFilterOperationsByTag(t *testing.T) { }, } + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + + // Get a spec from the test definition in this file: + swagger, err := loader.LoadFromData([]byte(testOpenAPIDefinition)) + assert.NoError(t, err) + + // Run our code generation: + code, err := Generate(swagger, opts) + assert.NoError(t, err) + assert.NotEmpty(t, code) + assert.Contains(t, code, `"/test/:name"`) + assert.NotContains(t, code, `"/cat"`) + }) +} + +func TestFilterOperationsByOperationID(t *testing.T) { + packageName := "testswagger" + t.Run("include operation ids", func(t *testing.T) { + opts := Configuration{ + PackageName: packageName, + Generate: GenerateOptions{ + EchoServer: true, + Client: true, + Models: true, + EmbeddedSpec: true, + }, + OutputOptions: OutputOptions{ + IncludeOperationIDs: []string{"getCatStatus"}, + }, + } + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + + // Get a spec from the test definition in this file: + swagger, err := loader.LoadFromData([]byte(testOpenAPIDefinition)) + assert.NoError(t, err) + + // Run our code generation: + code, err := Generate(swagger, opts) + assert.NoError(t, err) + assert.NotEmpty(t, code) + assert.NotContains(t, code, `"/test/:name"`) + assert.Contains(t, code, `"/cat"`) + }) + + t.Run("exclude operation ids", func(t *testing.T) { + opts := Configuration{ + PackageName: packageName, + Generate: GenerateOptions{ + EchoServer: true, + Client: true, + Models: true, + EmbeddedSpec: true, + }, + OutputOptions: OutputOptions{ + ExcludeOperationIDs: []string{"getCatStatus"}, + }, + } + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + // Get a spec from the test definition in this file: - swagger, err := openapi3.NewLoader().LoadFromData([]byte(testOpenAPIDefinition)) + swagger, err := loader.LoadFromData([]byte(testOpenAPIDefinition)) assert.NoError(t, err) // Run our code generation: diff --git a/pkg/codegen/gather.go b/pkg/codegen/gather.go new file mode 100644 index 0000000000..84bd7e55df --- /dev/null +++ b/pkg/codegen/gather.go @@ -0,0 +1,331 @@ +package codegen + +import ( + "fmt" + "sort" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/oapi-codegen/oapi-codegen/v2/pkg/util" +) + +// SchemaPath represents the document location of a schema, e.g. +// ["components", "schemas", "Pet", "properties", "name"]. +type SchemaPath []string + +// String returns the path joined with "/". +func (sp SchemaPath) String() string { + return strings.Join(sp, "/") +} + +// SchemaContext identifies where in the OpenAPI document a schema was found. +type SchemaContext int + +const ( + ContextComponentSchema SchemaContext = iota + ContextComponentParameter + ContextComponentRequestBody + ContextComponentResponse + ContextComponentHeader + ContextOperationParameter + ContextOperationRequestBody + ContextOperationResponse + ContextClientResponseWrapper +) + +// String returns a human-readable name for the context. +func (sc SchemaContext) String() string { + switch sc { + case ContextComponentSchema: + return "Schema" + case ContextComponentParameter: + return "Parameter" + case ContextComponentRequestBody: + return "RequestBody" + case ContextComponentResponse: + return "Response" + case ContextComponentHeader: + return "Header" + case ContextOperationParameter: + return "OperationParameter" + case ContextOperationRequestBody: + return "OperationRequestBody" + case ContextOperationResponse: + return "OperationResponse" + case ContextClientResponseWrapper: + return "ClientResponseWrapper" + default: + return "Unknown" + } +} + +// Suffix returns the suffix to use for collision resolution. +func (sc SchemaContext) Suffix() string { + switch sc { + case ContextComponentSchema: + return "Schema" + case ContextComponentParameter, ContextOperationParameter: + return "Parameter" + case ContextComponentRequestBody, ContextOperationRequestBody: + return "RequestBody" + case ContextComponentResponse, ContextOperationResponse: + return "Response" + case ContextComponentHeader: + return "Header" + case ContextClientResponseWrapper: + return "Response" + default: + return "" + } +} + +// GatheredSchema represents a schema discovered during the gather pass, +// along with its document location and context metadata. +type GatheredSchema struct { + Path SchemaPath + Context SchemaContext + Ref string // $ref string if this is a reference + Schema *openapi3.Schema // The resolved schema value + OperationID string // Enclosing operation's ID, if any + ContentType string // Media type, if from request/response body + StatusCode string // HTTP status code, if from a response + ParamIndex int // Parameter index within an operation + ComponentName string // The component name (e.g., "Bar" for components/schemas/Bar) + GoNameOverride string // x-go-name override from the component or its parent container +} + +// IsComponentSchema returns true if this schema came from components/schemas. +func (gs *GatheredSchema) IsComponentSchema() bool { + return gs.Context == ContextComponentSchema +} + +// GatherSchemas walks the entire OpenAPI spec and collects all schemas that +// will need Go type names. This is the first pass of the multi-pass resolution. +func GatherSchemas(spec *openapi3.T, opts Configuration) []*GatheredSchema { + var schemas []*GatheredSchema + + if spec.Components != nil { + schemas = append(schemas, gatherComponentSchemas(spec.Components)...) + schemas = append(schemas, gatherComponentParameters(spec.Components)...) + schemas = append(schemas, gatherComponentResponses(spec.Components)...) + schemas = append(schemas, gatherComponentRequestBodies(spec.Components)...) + schemas = append(schemas, gatherComponentHeaders(spec.Components)...) + } + + // Gather client response wrapper types for operations that will generate + // client code. These synthetic entries exist so wrapper types like + // `CreateChatCompletionResponse` participate in collision detection. + if opts.Generate.Client { + schemas = append(schemas, gatherClientResponseWrappers(spec)...) + } + + return schemas +} + +func gatherComponentSchemas(components *openapi3.Components) []*GatheredSchema { + var result []*GatheredSchema + for _, name := range SortedSchemaKeys(components.Schemas) { + schemaRef := components.Schemas[name] + if schemaRef == nil || schemaRef.Value == nil { + continue + } + var goNameOverride string + if schemaRef.Ref == "" { + goNameOverride = extractGoNameOverride(schemaRef.Value.Extensions) + } + result = append(result, &GatheredSchema{ + Path: SchemaPath{"components", "schemas", name}, + Context: ContextComponentSchema, + Ref: schemaRef.Ref, + Schema: schemaRef.Value, + ComponentName: name, + GoNameOverride: goNameOverride, + }) + } + return result +} + +func gatherComponentParameters(components *openapi3.Components) []*GatheredSchema { + var result []*GatheredSchema + for _, name := range SortedMapKeys(components.Parameters) { + paramRef := components.Parameters[name] + if paramRef == nil || paramRef.Value == nil { + continue + } + param := paramRef.Value + if param.Schema != nil && param.Schema.Value != nil { + var goNameOverride string + if paramRef.Ref == "" { + goNameOverride = extractGoNameOverride(param.Extensions) + } + result = append(result, &GatheredSchema{ + Path: SchemaPath{"components", "parameters", name}, + Context: ContextComponentParameter, + Ref: paramRef.Ref, + Schema: param.Schema.Value, + ComponentName: name, + GoNameOverride: goNameOverride, + }) + } + } + return result +} + +func gatherComponentResponses(components *openapi3.Components) []*GatheredSchema { + var result []*GatheredSchema + for _, name := range SortedMapKeys(components.Responses) { + responseRef := components.Responses[name] + if responseRef == nil || responseRef.Value == nil { + continue + } + response := responseRef.Value + var goNameOverride string + if responseRef.Ref == "" { + goNameOverride = extractGoNameOverride(response.Extensions) + } + for _, mediaType := range SortedMapKeys(response.Content) { + if !util.IsMediaTypeJson(mediaType) { + continue + } + mt := response.Content[mediaType] + if mt.Schema != nil && mt.Schema.Value != nil { + result = append(result, &GatheredSchema{ + Path: SchemaPath{"components", "responses", name, "content", mediaType}, + Context: ContextComponentResponse, + Ref: responseRef.Ref, + Schema: mt.Schema.Value, + ContentType: mediaType, + ComponentName: name, + GoNameOverride: goNameOverride, + }) + } + } + } + return result +} + +func gatherComponentRequestBodies(components *openapi3.Components) []*GatheredSchema { + var result []*GatheredSchema + for _, name := range SortedMapKeys(components.RequestBodies) { + bodyRef := components.RequestBodies[name] + if bodyRef == nil || bodyRef.Value == nil { + continue + } + body := bodyRef.Value + var goNameOverride string + if bodyRef.Ref == "" { + goNameOverride = extractGoNameOverride(body.Extensions) + } + for _, mediaType := range SortedMapKeys(body.Content) { + if !util.IsMediaTypeJson(mediaType) { + continue + } + mt := body.Content[mediaType] + if mt.Schema != nil && mt.Schema.Value != nil { + result = append(result, &GatheredSchema{ + Path: SchemaPath{"components", "requestBodies", name, "content", mediaType}, + Context: ContextComponentRequestBody, + Ref: bodyRef.Ref, + Schema: mt.Schema.Value, + ContentType: mediaType, + ComponentName: name, + GoNameOverride: goNameOverride, + }) + } + } + } + return result +} + +func gatherComponentHeaders(components *openapi3.Components) []*GatheredSchema { + var result []*GatheredSchema + for _, name := range SortedMapKeys(components.Headers) { + headerRef := components.Headers[name] + if headerRef == nil || headerRef.Value == nil { + continue + } + header := headerRef.Value + if header.Schema != nil && header.Schema.Value != nil { + var goNameOverride string + if headerRef.Ref == "" { + goNameOverride = extractGoNameOverride(header.Extensions) + } + result = append(result, &GatheredSchema{ + Path: SchemaPath{"components", "headers", name}, + Context: ContextComponentHeader, + Ref: headerRef.Ref, + Schema: header.Schema.Value, + ComponentName: name, + GoNameOverride: goNameOverride, + }) + } + } + return result +} + +// gatherClientResponseWrappers creates synthetic schema entries for each +// operation that would generate a client response wrapper type like +// `Response`. These don't correspond to a real schema in the +// spec but they need names that don't collide with real types. +func gatherClientResponseWrappers(spec *openapi3.T) []*GatheredSchema { + var result []*GatheredSchema + + if spec.Paths == nil { + return result + } + + // Collect all operations sorted for determinism + type opEntry struct { + path string + method string + op *openapi3.Operation + } + var ops []opEntry + + pathKeys := SortedMapKeys(spec.Paths.Map()) + for _, path := range pathKeys { + pathItem := spec.Paths.Find(path) + if pathItem == nil { + continue + } + for method, op := range pathItem.Operations() { + if op != nil && op.OperationID != "" { + ops = append(ops, opEntry{path: path, method: method, op: op}) + } + } + } + + // Sort by operationID for determinism + sort.Slice(ops, func(i, j int) bool { + return ops[i].op.OperationID < ops[j].op.OperationID + }) + + for _, entry := range ops { + result = append(result, &GatheredSchema{ + Path: SchemaPath{"paths", entry.path, entry.method, "x-client-response-wrapper"}, + Context: ContextClientResponseWrapper, + OperationID: entry.op.OperationID, + }) + } + + return result +} + +// FormatPath returns a human-readable representation of the path for debugging. +func (gs *GatheredSchema) FormatPath() string { + return fmt.Sprintf("#/%s", strings.Join(gs.Path, "/")) +} + +// extractGoNameOverride reads the x-go-name extension from extensions and +// returns its value, or "" if not present or invalid. +func extractGoNameOverride(extensions map[string]any) string { + ext, ok := extensions[extGoName] + if !ok { + return "" + } + name, err := extTypeName(ext) + if err != nil { + return "" + } + return name +} diff --git a/pkg/codegen/gather_test.go b/pkg/codegen/gather_test.go new file mode 100644 index 0000000000..24e63b000b --- /dev/null +++ b/pkg/codegen/gather_test.go @@ -0,0 +1,356 @@ +package codegen + +import ( + "testing" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGatherSchemas_ComponentSchemas(t *testing.T) { + spec := &openapi3.T{ + Components: &openapi3.Components{ + Schemas: openapi3.Schemas{ + "Pet": &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"object"}}, + }, + "Owner": &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"object"}}, + }, + }, + }, + } + + opts := Configuration{} + schemas := GatherSchemas(spec, opts) + + require.Len(t, schemas, 2) + + // Sorted order: Owner, Pet + assert.Equal(t, SchemaPath{"components", "schemas", "Owner"}, schemas[0].Path) + assert.Equal(t, ContextComponentSchema, schemas[0].Context) + assert.Equal(t, "Owner", schemas[0].ComponentName) + + assert.Equal(t, SchemaPath{"components", "schemas", "Pet"}, schemas[1].Path) + assert.Equal(t, ContextComponentSchema, schemas[1].Context) + assert.Equal(t, "Pet", schemas[1].ComponentName) +} + +func TestGatherSchemas_ComponentParameters(t *testing.T) { + spec := &openapi3.T{ + Components: &openapi3.Components{ + Parameters: openapi3.ParametersMap{ + "Limit": &openapi3.ParameterRef{ + Value: &openapi3.Parameter{ + Name: "limit", + In: "query", + Schema: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"integer"}}, + }, + }, + }, + }, + }, + } + + opts := Configuration{} + schemas := GatherSchemas(spec, opts) + + require.Len(t, schemas, 1) + assert.Equal(t, SchemaPath{"components", "parameters", "Limit"}, schemas[0].Path) + assert.Equal(t, ContextComponentParameter, schemas[0].Context) + assert.Equal(t, "Limit", schemas[0].ComponentName) +} + +func TestGatherSchemas_ComponentResponses(t *testing.T) { + spec := &openapi3.T{ + Components: &openapi3.Components{ + Responses: openapi3.ResponseBodies{ + "Error": &openapi3.ResponseRef{ + Value: &openapi3.Response{ + Content: openapi3.Content{ + "application/json": &openapi3.MediaType{ + Schema: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"object"}}, + }, + }, + }, + }, + }, + }, + }, + } + + opts := Configuration{} + schemas := GatherSchemas(spec, opts) + + require.Len(t, schemas, 1) + assert.Equal(t, SchemaPath{"components", "responses", "Error", "content", "application/json"}, schemas[0].Path) + assert.Equal(t, ContextComponentResponse, schemas[0].Context) + assert.Equal(t, "Error", schemas[0].ComponentName) + assert.Equal(t, "application/json", schemas[0].ContentType) +} + +func TestGatherSchemas_ComponentRequestBodies(t *testing.T) { + spec := &openapi3.T{ + Components: &openapi3.Components{ + RequestBodies: openapi3.RequestBodies{ + "CreatePet": &openapi3.RequestBodyRef{ + Value: &openapi3.RequestBody{ + Content: openapi3.Content{ + "application/json": &openapi3.MediaType{ + Schema: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"object"}}, + }, + }, + }, + }, + }, + }, + }, + } + + opts := Configuration{} + schemas := GatherSchemas(spec, opts) + + require.Len(t, schemas, 1) + assert.Equal(t, SchemaPath{"components", "requestBodies", "CreatePet", "content", "application/json"}, schemas[0].Path) + assert.Equal(t, ContextComponentRequestBody, schemas[0].Context) + assert.Equal(t, "CreatePet", schemas[0].ComponentName) +} + +func TestGatherSchemas_ComponentHeaders(t *testing.T) { + spec := &openapi3.T{ + Components: &openapi3.Components{ + Headers: openapi3.Headers{ + "X-Rate-Limit": &openapi3.HeaderRef{ + Value: &openapi3.Header{ + Parameter: openapi3.Parameter{ + Schema: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"integer"}}, + }, + }, + }, + }, + }, + }, + } + + opts := Configuration{} + schemas := GatherSchemas(spec, opts) + + require.Len(t, schemas, 1) + assert.Equal(t, SchemaPath{"components", "headers", "X-Rate-Limit"}, schemas[0].Path) + assert.Equal(t, ContextComponentHeader, schemas[0].Context) +} + +func TestGatherSchemas_ClientResponseWrappers(t *testing.T) { + paths := openapi3.NewPaths() + paths.Set("/pets", &openapi3.PathItem{ + Get: &openapi3.Operation{ + OperationID: "listPets", + }, + Post: &openapi3.Operation{ + OperationID: "createPet", + }, + }) + + spec := &openapi3.T{ + Paths: paths, + } + + // Without client generation, no wrappers + opts := Configuration{Generate: GenerateOptions{Client: false}} + schemas := GatherSchemas(spec, opts) + assert.Len(t, schemas, 0) + + // With client generation, wrappers are gathered + opts = Configuration{Generate: GenerateOptions{Client: true}} + schemas = GatherSchemas(spec, opts) + assert.Len(t, schemas, 2) + + // Check they're sorted by operationID + assert.Equal(t, ContextClientResponseWrapper, schemas[0].Context) + assert.Equal(t, "createPet", schemas[0].OperationID) + assert.Equal(t, ContextClientResponseWrapper, schemas[1].Context) + assert.Equal(t, "listPets", schemas[1].OperationID) +} + +func TestGatherSchemas_GoNameOverride_Schema(t *testing.T) { + spec := &openapi3.T{ + Components: &openapi3.Components{ + Schemas: openapi3.Schemas{ + "Renamer": &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + Extensions: map[string]any{"x-go-name": "SpecialName"}, + }, + }, + }, + }, + } + + opts := Configuration{} + schemas := GatherSchemas(spec, opts) + + require.Len(t, schemas, 1) + assert.Equal(t, "SpecialName", schemas[0].GoNameOverride) +} + +func TestGatherSchemas_GoNameOverride_Response(t *testing.T) { + spec := &openapi3.T{ + Components: &openapi3.Components{ + Responses: openapi3.ResponseBodies{ + "Outcome": &openapi3.ResponseRef{ + Value: &openapi3.Response{ + Extensions: map[string]any{"x-go-name": "OutcomeResult"}, + Content: openapi3.Content{ + "application/json": &openapi3.MediaType{ + Schema: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"object"}}, + }, + }, + }, + }, + }, + }, + }, + } + + opts := Configuration{} + schemas := GatherSchemas(spec, opts) + + require.Len(t, schemas, 1) + assert.Equal(t, "OutcomeResult", schemas[0].GoNameOverride) +} + +func TestGatherSchemas_GoNameOverride_RequestBody(t *testing.T) { + spec := &openapi3.T{ + Components: &openapi3.Components{ + RequestBodies: openapi3.RequestBodies{ + "Payload": &openapi3.RequestBodyRef{ + Value: &openapi3.RequestBody{ + Extensions: map[string]any{"x-go-name": "PayloadBody"}, + Content: openapi3.Content{ + "application/json": &openapi3.MediaType{ + Schema: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"object"}}, + }, + }, + }, + }, + }, + }, + }, + } + + opts := Configuration{} + schemas := GatherSchemas(spec, opts) + + require.Len(t, schemas, 1) + assert.Equal(t, "PayloadBody", schemas[0].GoNameOverride) +} + +func TestGatherSchemas_GoNameOverride_SkippedForRef(t *testing.T) { + spec := &openapi3.T{ + Components: &openapi3.Components{ + Schemas: openapi3.Schemas{ + "AliasedPet": &openapi3.SchemaRef{ + Ref: "#/components/schemas/Pet", + Value: &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + Extensions: map[string]any{"x-go-name": "ShouldBeIgnored"}, + }, + }, + }, + }, + } + + opts := Configuration{} + schemas := GatherSchemas(spec, opts) + + require.Len(t, schemas, 1) + assert.Equal(t, "", schemas[0].GoNameOverride) +} + +func TestGatherSchemas_AllSections(t *testing.T) { + // Spec with "Bar" in schemas, parameters, responses, requestBodies, headers + // This is the issue #200 scenario (cross-section collision) + paths := openapi3.NewPaths() + spec := &openapi3.T{ + Paths: paths, + Components: &openapi3.Components{ + Schemas: openapi3.Schemas{ + "Bar": &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"object"}}, + }, + }, + Parameters: openapi3.ParametersMap{ + "Bar": &openapi3.ParameterRef{ + Value: &openapi3.Parameter{ + Name: "Bar", + In: "query", + Schema: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}, + }, + }, + }, + }, + Responses: openapi3.ResponseBodies{ + "Bar": &openapi3.ResponseRef{ + Value: &openapi3.Response{ + Content: openapi3.Content{ + "application/json": &openapi3.MediaType{ + Schema: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"object"}}, + }, + }, + }, + }, + }, + }, + RequestBodies: openapi3.RequestBodies{ + "Bar": &openapi3.RequestBodyRef{ + Value: &openapi3.RequestBody{ + Content: openapi3.Content{ + "application/json": &openapi3.MediaType{ + Schema: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"object"}}, + }, + }, + }, + }, + }, + }, + Headers: openapi3.Headers{ + "Bar": &openapi3.HeaderRef{ + Value: &openapi3.Header{ + Parameter: openapi3.Parameter{ + Schema: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"boolean"}}, + }, + }, + }, + }, + }, + }, + } + + opts := Configuration{} + schemas := GatherSchemas(spec, opts) + + // Should have 5 entries: schema, parameter, response, requestBody, header + assert.Len(t, schemas, 5) + + // Verify contexts are all different + contexts := make(map[SchemaContext]bool) + for _, s := range schemas { + contexts[s.Context] = true + } + assert.True(t, contexts[ContextComponentSchema]) + assert.True(t, contexts[ContextComponentParameter]) + assert.True(t, contexts[ContextComponentResponse]) + assert.True(t, contexts[ContextComponentRequestBody]) + assert.True(t, contexts[ContextComponentHeader]) +} diff --git a/pkg/codegen/inline.go b/pkg/codegen/inline.go index e722337fac..459255c05f 100644 --- a/pkg/codegen/inline.go +++ b/pkg/codegen/inline.go @@ -32,22 +32,22 @@ func GenerateInlinedSpec(t *template.Template, importMapping importMap, swagger // Marshal to json encoded, err := swagger.MarshalJSON() if err != nil { - return "", fmt.Errorf("error marshaling swagger: %s", err) + return "", fmt.Errorf("error marshaling swagger: %w", err) } // gzip var buf bytes.Buffer zw, err := gzip.NewWriterLevel(&buf, gzip.BestCompression) if err != nil { - return "", fmt.Errorf("error creating gzip compressor: %s", err) + return "", fmt.Errorf("error creating gzip compressor: %w", err) } _, err = zw.Write(encoded) if err != nil { - return "", fmt.Errorf("error gzipping swagger file: %s", err) + return "", fmt.Errorf("error gzipping swagger file: %w", err) } err = zw.Close() if err != nil { - return "", fmt.Errorf("error gzipping swagger file: %s", err) + return "", fmt.Errorf("error gzipping swagger file: %w", err) } str := base64.StdEncoding.EncodeToString(buf.Bytes()) diff --git a/pkg/codegen/merge_schemas.go b/pkg/codegen/merge_schemas.go index ba5b86db51..39c499c7da 100644 --- a/pkg/codegen/merge_schemas.go +++ b/pkg/codegen/merge_schemas.go @@ -53,10 +53,10 @@ func valueWithPropagatedRef(ref *openapi3.SchemaRef) (openapi3.Schema, error) { } pathParts := strings.Split(ref.Ref, "#") - if len(pathParts) != 2 { + if len(pathParts) < 1 || len(pathParts) > 2 { return openapi3.Schema{}, fmt.Errorf("unsupported reference: %s", ref.Ref) } - remoteComponent, _ := pathParts[0], pathParts[1] + remoteComponent := pathParts[0] // remote ref schema := *ref.Value @@ -86,19 +86,14 @@ func mergeAllOf(allOf []*openapi3.SchemaRef) (openapi3.Schema, error) { // all of whose fields are composed. func mergeOpenapiSchemas(s1, s2 openapi3.Schema, allOf bool) (openapi3.Schema, error) { var result openapi3.Schema - if s1.Extensions != nil || s2.Extensions != nil { - result.Extensions = make(map[string]interface{}) - if s1.Extensions != nil { - for k, v := range s1.Extensions { - result.Extensions[k] = v - } - } - if s2.Extensions != nil { - for k, v := range s2.Extensions { - // TODO: Check for collisions - result.Extensions[k] = v - } - } + + result.Extensions = make(map[string]any) + for k, v := range s1.Extensions { + result.Extensions[k] = v + } + for k, v := range s2.Extensions { + // TODO: Check for collisions + result.Extensions[k] = v } result.OneOf = append(s1.OneOf, s2.OneOf...) @@ -125,8 +120,8 @@ func mergeOpenapiSchemas(s1, s2 openapi3.Schema, allOf bool) (openapi3.Schema, e result.AllOf = append(s1.AllOf, s2.AllOf...) - if s1.Type != "" && s2.Type != "" && s1.Type != s2.Type { - return openapi3.Schema{}, errors.New("can not merge incompatible types") + if s1.Type.Slice() != nil && s2.Type.Slice() != nil && !equalTypes(s1.Type, s2.Type) { + return openapi3.Schema{}, fmt.Errorf("can not merge incompatible types: %v, %v", s1.Type.Slice(), s2.Type.Slice()) } result.Type = s1.Type @@ -209,14 +204,22 @@ func mergeOpenapiSchemas(s1, s2 openapi3.Schema, allOf bool) (openapi3.Schema, e result.Properties[k] = v } - if SchemaHasAdditionalProperties(&s1) && SchemaHasAdditionalProperties(&s2) { - return openapi3.Schema{}, errors.New("merging two schemas with additional properties, this is unhandled") - } - if s1.AdditionalProperties.Schema != nil { - result.AdditionalProperties.Schema = s1.AdditionalProperties.Schema - } - if s2.AdditionalProperties.Schema != nil { - result.AdditionalProperties.Schema = s2.AdditionalProperties.Schema + if isAdditionalPropertiesExplicitFalse(&s1) || isAdditionalPropertiesExplicitFalse(&s2) { + result.WithoutAdditionalProperties() + } else if s1.AdditionalProperties.Schema != nil { + if s2.AdditionalProperties.Schema != nil { + return openapi3.Schema{}, errors.New("merging two schemas with additional properties, this is unhandled") + } else { + result.AdditionalProperties.Schema = s1.AdditionalProperties.Schema + } + } else { + if s2.AdditionalProperties.Schema != nil { + result.AdditionalProperties.Schema = s2.AdditionalProperties.Schema + } else { + if s1.AdditionalProperties.Has != nil || s2.AdditionalProperties.Has != nil { + result.WithAnyAdditionalProperties() + } + } } // Allow discriminators for allOf merges, but disallow for one/anyOfs. @@ -226,3 +229,21 @@ func mergeOpenapiSchemas(s1, s2 openapi3.Schema, allOf bool) (openapi3.Schema, e return result, nil } + +func equalTypes(t1 *openapi3.Types, t2 *openapi3.Types) bool { + s1 := t1.Slice() + s2 := t2.Slice() + + if len(s1) != len(s2) { + return false + } + + // NOTE that ideally we'd use `slices.Equal` but as we're currently supporting Go 1.20+, we can't use it (yet https://github.com/oapi-codegen/oapi-codegen/issues/1634) + for i := range s1 { + if s1[i] != s2[i] { + return false + } + } + + return true +} diff --git a/pkg/codegen/minimum_go_version.go b/pkg/codegen/minimum_go_version.go new file mode 100644 index 0000000000..4f4f70fe7b --- /dev/null +++ b/pkg/codegen/minimum_go_version.go @@ -0,0 +1,91 @@ +package codegen + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + "golang.org/x/mod/modfile" +) + +const maximumDepthToSearchForGoMod = 5 + +// minimumGoVersionForGenerateStdHTTPServer indicates the Go 1.x minor version that the module the std-http-server is being generated into needs. +// If the version is lower, a warning should be logged. +const minimumGoVersionForGenerateStdHTTPServer = 22 + +func findAndParseGoModuleForDepth(dir string, maxDepth int) (string, *modfile.File, error) { + absDir, err := filepath.Abs(dir) + if err != nil { + return "", nil, fmt.Errorf("failed to determine absolute path for %v: %w", dir, err) + } + currentDir := absDir + + for i := 0; i <= maxDepth; i++ { + goModPath := filepath.Join(currentDir, "go.mod") + if _, err := os.Stat(goModPath); err == nil { + goModContent, err := os.ReadFile(goModPath) + if err != nil { + return "", nil, fmt.Errorf("failed to read `go.mod`: %w", err) + } + + mod, err := modfile.ParseLax("go.mod", goModContent, nil) + if err != nil { + return "", nil, fmt.Errorf("failed to parse `go.mod`: %w", err) + } + + return goModPath, mod, nil + } + + goModPath = filepath.Join(currentDir, "tools.mod") + if _, err := os.Stat(goModPath); err == nil { + goModContent, err := os.ReadFile(goModPath) + if err != nil { + return "", nil, fmt.Errorf("failed to read `tools.mod`: %w", err) + } + + parsedModFile, err := modfile.ParseLax("tools.mod", goModContent, nil) + if err != nil { + return "", nil, fmt.Errorf("failed to parse `tools.mod`: %w", err) + } + + return goModPath, parsedModFile, nil + } + + parentDir := filepath.Dir(currentDir) + // NOTE that this may not work particularly well on Windows + if parentDir == "/" { + break + } + + currentDir = parentDir + } + + return "", nil, fmt.Errorf("no `go.mod` or `tools.mod` file found within %d levels upwards from %s", maxDepth, absDir) +} + +// hasMinimalMinorGoDirective indicates that the Go module (`mod`) has a minor version greater than or equal to the `expected`'s +// This only applies to the `go` directive: +// +// go 1.23 +// go 1.22.1 +func hasMinimalMinorGoDirective(expected int, mod *modfile.File) bool { + parts := strings.Split(mod.Go.Version, ".") + + if len(parts) < 2 { + return false + } + + actual, err := strconv.Atoi(parts[1]) + if err != nil { + return false + } + + if actual < expected { + return false + } + + return true +} diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index 7de40d61cc..5d9f735506 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -23,8 +23,9 @@ import ( "text/template" "unicode" - "github.com/deepmap/oapi-codegen/pkg/util" "github.com/getkin/kin-openapi/openapi3" + + "github.com/oapi-codegen/oapi-codegen/v2/pkg/util" ) type ParameterDefinition struct { @@ -43,6 +44,23 @@ func (pd ParameterDefinition) TypeDef() string { return typeDecl } +// RequiresNilCheck indicates whether the generated property should have a nil check performed on it before other checks. +// This should be used in templates when performing `nil` checks, but NOT when i.e. determining if there should be an optional pointer given to the type - in that case, use `HasOptionalPointer` +func (pd ParameterDefinition) RequiresNilCheck() bool { + return pd.ZeroValueIsNil() || pd.HasOptionalPointer() +} + +// ZeroValueIsNil is a helper function to determine if the given Go type used for this property +// Will return true if the OpenAPI `type` is: +// - `array` +func (pd ParameterDefinition) ZeroValueIsNil() bool { + if pd.Schema.OAPISchema == nil { + return false + } + + return pd.Schema.OAPISchema.Type.Is("array") +} + // JsonTag generates the JSON annotation to map GoType to json type name. If Parameter // Foo is marshaled to json as "foo", this will create the annotation // 'json:"foo"' @@ -113,8 +131,28 @@ func (pd *ParameterDefinition) Explode() bool { return *pd.Spec.Explode } +// SchemaType returns the first OpenAPI type string for this parameter's schema (e.g. "string", "integer"), +// or empty string if unavailable. +func (pd *ParameterDefinition) SchemaType() string { + if pd.Spec.Schema != nil && pd.Spec.Schema.Value != nil && pd.Spec.Schema.Value.Type != nil { + if s := pd.Spec.Schema.Value.Type.Slice(); len(s) > 0 { + return s[0] + } + } + return "" +} + +// SchemaFormat returns the OpenAPI format string for this parameter's schema (e.g. "byte", "date-time"), +// or empty string if unavailable. +func (pd *ParameterDefinition) SchemaFormat() string { + if pd.Spec.Schema != nil && pd.Spec.Schema.Value != nil { + return pd.Spec.Schema.Value.Format + } + return "" +} + func (pd ParameterDefinition) GoVariableName() string { - name := LowercaseFirstCharacter(pd.GoName()) + name := LowercaseFirstCharacters(pd.GoName()) if IsGoKeyword(name) { name = "p" + UppercaseFirstCharacter(name) } @@ -126,18 +164,25 @@ func (pd ParameterDefinition) GoVariableName() string { func (pd ParameterDefinition) GoName() string { goName := pd.ParamName - if _, ok := pd.Spec.Extensions[extGoName]; ok { - if extGoFieldName, err := extParseGoFieldName(pd.Spec.Extensions[extGoName]); err == nil { + if extension, ok := pd.Spec.Extensions[extGoName]; ok { + if extGoFieldName, err := extParseGoFieldName(extension); err == nil { goName = extGoFieldName } } return SchemaNameToTypeName(goName) } +// Deprecated: Use HasOptionalPointer, as it is clearer what the intent is. func (pd ParameterDefinition) IndirectOptional() bool { return !pd.Required && !pd.Schema.SkipOptionalPointer } +// HasOptionalPointer indicates whether the generated property has an optional pointer associated with it. +// This takes into account the `x-go-type-skip-optional-pointer` extension, allowing a parameter definition to control whether the pointer should be skipped. +func (pd ParameterDefinition) HasOptionalPointer() bool { + return pd.Required == false && pd.Schema.SkipOptionalPointer == false //nolint:staticcheck +} + type ParameterDefinitions []ParameterDefinition func (p ParameterDefinitions) FindByName(name string) *ParameterDefinition { @@ -196,7 +241,7 @@ func DescribeSecurityDefinition(securityRequirements openapi3.SecurityRequiremen outDefs := make([]SecurityDefinition, 0) for _, sr := range securityRequirements { - for _, k := range SortedSecurityRequirementKeys(sr) { + for _, k := range SortedMapKeys(sr) { v := sr[k] outDefs = append(outDefs, SecurityDefinition{ProviderName: k, Scopes: v}) } @@ -207,7 +252,8 @@ func DescribeSecurityDefinition(securityRequirements openapi3.SecurityRequiremen // OperationDefinition describes an Operation type OperationDefinition struct { - OperationId string // The operation_id description from Swagger, used to generate function names + // OperationId is the `operationId` field from the OpenAPI Specification, after going through a `nameNormalizer`, and will be used to generate function names + OperationId string PathParams []ParameterDefinition // Parameters in the path, eg, /path/:param HeaderParams []ParameterDefinition // Parameters in HTTP headers @@ -274,33 +320,75 @@ func (o *OperationDefinition) SummaryAsComment() string { func (o *OperationDefinition) GetResponseTypeDefinitions() ([]ResponseTypeDefinition, error) { var tds []ResponseTypeDefinition - responses := o.Spec.Responses - sortedResponsesKeys := SortedResponsesKeys(responses) + if o.Spec == nil || o.Spec.Responses == nil { + return tds, nil + } + + sortedResponsesKeys := SortedMapKeys(o.Spec.Responses.Map()) for _, responseName := range sortedResponsesKeys { - responseRef := responses[responseName] + responseRef := o.Spec.Responses.Value(responseName) // We can only generate a type if we have a value: if responseRef.Value != nil { - sortedContentKeys := SortedContentKeys(responseRef.Value.Content) + jsonCount := 0 + for mediaType := range responseRef.Value.Content { + if util.IsMediaTypeJson(mediaType) { + jsonCount++ + } + } + + sortedContentKeys := SortedMapKeys(responseRef.Value.Content) for _, contentTypeName := range sortedContentKeys { contentType := responseRef.Value.Content[contentTypeName] // We can only generate a type if we have a schema: if contentType.Schema != nil { - responseSchema, err := GenerateGoSchema(contentType.Schema, []string{responseName}) + // When a response has multiple JSON content types (e.g., + // application/json, application/json-patch+json, and + // application/merge-patch+json), we include a content-type-derived + // segment in the schema path. This is necessary because + // GenerateGoSchema uses the path to name any inline types it + // creates (e.g., oneOf union members). Without the content type + // in the path, all content types for the same response produce + // identically-named inline types. If those content types have + // different schemas, the result is conflicting type declarations; + // if they have the same schema, the result is duplicate + // declarations. Both cases produce code that won't compile. + // + // We only add the content type segment when collision resolution + // is enabled (resolve-type-name-collisions) and jsonCount > 1, + // to avoid changing type names for existing users. Ideally the + // media type would always be part of the path for consistency. + // TODO: revisit this at the next major version change — + // always include the media type in the schema path. + schemaPath := []string{o.OperationId, responseName} + if jsonCount > 1 && util.IsMediaTypeJson(contentTypeName) && globalState.options.OutputOptions.ResolveTypeNameCollisions { + schemaPath = append(schemaPath, mediaTypeToCamelCase(contentTypeName)) + } + responseSchema, err := GenerateGoSchema(contentType.Schema, schemaPath) if err != nil { - return nil, fmt.Errorf("Unable to determine Go type for %s.%s: %w", o.OperationId, contentTypeName, err) + return nil, fmt.Errorf("unable to determine Go type for %s.%s: %w", o.OperationId, contentTypeName, err) } var typeName string switch { - case StringInArray(contentTypeName, contentTypesJSON): - typeName = fmt.Sprintf("JSON%s", ToCamelCase(responseName)) + + // HAL+JSON: + case StringInArray(contentTypeName, contentTypesHalJSON): + typeName = fmt.Sprintf("HALJSON%s", nameNormalizer(responseName)) + case contentTypeName == "application/json": + // if it's the standard application/json + typeName = fmt.Sprintf("JSON%s", nameNormalizer(responseName)) + // Vendored JSON + case StringInArray(contentTypeName, contentTypesJSON) || util.IsMediaTypeJson(contentTypeName): + baseTypeName := fmt.Sprintf("%s%s", nameNormalizer(contentTypeName), nameNormalizer(responseName)) + + typeName = strings.ReplaceAll(baseTypeName, "Json", "JSON") // YAML: case StringInArray(contentTypeName, contentTypesYAML): - typeName = fmt.Sprintf("YAML%s", ToCamelCase(responseName)) + typeName = fmt.Sprintf("YAML%s", nameNormalizer(responseName)) // XML: case StringInArray(contentTypeName, contentTypesXML): - typeName = fmt.Sprintf("XML%s", ToCamelCase(responseName)) + typeName = fmt.Sprintf("XML%s", nameNormalizer(responseName)) default: continue } @@ -310,14 +398,22 @@ func (o *OperationDefinition) GetResponseTypeDefinitions() ([]ResponseTypeDefini TypeName: typeName, Schema: responseSchema, }, - ResponseName: responseName, - ContentTypeName: contentTypeName, + ResponseName: responseName, + ContentTypeName: contentTypeName, + AdditionalTypeDefinitions: responseSchema.GetAdditionalTypeDefs(), } - if IsGoTypeReference(contentType.Schema.Ref) { - refType, err := RefPathToGoType(contentType.Schema.Ref) + if IsGoTypeReference(responseRef.Ref) { + refType, err := RefPathToGoType(responseRef.Ref) if err != nil { return nil, fmt.Errorf("error dereferencing response Ref: %w", err) } + if jsonCount > 1 && util.IsMediaTypeJson(contentTypeName) { + if resolved := resolvedNameForRefPath(responseRef.Ref, contentTypeName); resolved != "" { + refType = resolved + mediaTypeToCamelCase(contentTypeName) + } else { + refType += mediaTypeToCamelCase(contentTypeName) + } + } td.Schema.RefType = refType } tds = append(tds, td) @@ -388,7 +484,15 @@ func (r RequestBodyDefinition) Suffix() string { // IsSupportedByClient returns true if we support this content type for client. Otherwise only generic method will ge generated func (r RequestBodyDefinition) IsSupportedByClient() bool { - return r.NameTag == "JSON" || r.NameTag == "Formdata" || r.NameTag == "Text" + return r.IsJSON() || r.NameTag == "Formdata" || r.NameTag == "Text" +} + +// IsJSON returns whether this is a JSON media type, for instance: +// - application/json +// - application/vnd.api+json +// - application/*+json +func (r RequestBodyDefinition) IsJSON() bool { + return util.IsMediaTypeJson(r.ContentType) } // IsSupported returns true if we support this content type for server. Otherwise io.Reader will be generated @@ -428,6 +532,13 @@ func (r ResponseDefinition) IsRef() bool { return r.Ref != "" } +func (r ResponseDefinition) IsExternalRef() bool { + if !r.IsRef() { + return false + } + return strings.Contains(r.Ref, ".") +} + type ResponseContentDefinition struct { // This is the schema describing this content Schema Schema @@ -464,6 +575,14 @@ func (r ResponseContentDefinition) NameTagOrContentType() string { return SchemaNameToTypeName(r.ContentType) } +// IsJSON returns whether this is a JSON media type, for instance: +// - application/json +// - application/vnd.api+json +// - application/*+json +func (r ResponseContentDefinition) IsJSON() bool { + return util.IsMediaTypeJson(r.ContentType) +} + type ResponseHeaderDefinition struct { Name string GoName string @@ -483,11 +602,22 @@ func FilterParameterDefinitionByType(params []ParameterDefinition, in string) [] } // OperationDefinitions returns all operations for a swagger definition. -func OperationDefinitions(swagger *openapi3.T) ([]OperationDefinition, error) { +func OperationDefinitions(swagger *openapi3.T, initialismOverrides bool) ([]OperationDefinition, error) { var operations []OperationDefinition - for _, requestPath := range SortedPathsKeys(swagger.Paths) { - pathItem := swagger.Paths[requestPath] + var toCamelCaseFunc func(string) string + if initialismOverrides { + toCamelCaseFunc = ToCamelCaseWithInitialism + } else { + toCamelCaseFunc = ToCamelCase + } + + if swagger == nil || swagger.Paths == nil { + return operations, nil + } + + for _, requestPath := range SortedMapKeys(swagger.Paths.Map()) { + pathItem := swagger.Paths.Value(requestPath) // These are parameters defined for all methods on a given path. They // are shared by all methods. globalParams, err := DescribeParameters(pathItem.Parameters, nil) @@ -498,33 +628,47 @@ func OperationDefinitions(swagger *openapi3.T) ([]OperationDefinition, error) { // Each path can have a number of operations, POST, GET, OPTIONS, etc. pathOps := pathItem.Operations() - for _, opName := range SortedOperationsKeys(pathOps) { + for _, opName := range SortedMapKeys(pathOps) { + // NOTE that this is a reference to the existing copy of the Operation, so any modifications will modify our shared copy of the spec op := pathOps[opName] + if pathItem.Servers != nil { op.Servers = &pathItem.Servers } + // take a copy of operationId, so we don't modify the underlying spec + operationId := op.OperationID // We rely on OperationID to generate function names, it's required - if op.OperationID == "" { - op.OperationID, err = generateDefaultOperationID(opName, requestPath) + if operationId == "" { + operationId, err = generateDefaultOperationID(opName, requestPath, toCamelCaseFunc) if err != nil { return nil, fmt.Errorf("error generating default OperationID for %s/%s: %s", opName, requestPath, err) } } else { - op.OperationID = ToCamelCase(op.OperationID) + operationId = nameNormalizer(operationId) + } + operationId = typeNamePrefix(operationId) + operationId + + if !globalState.options.Compatibility.PreserveOriginalOperationIdCasingInEmbeddedSpec { + // update the existing, shared, copy of the spec if we're not wanting to preserve it + op.OperationID = operationId } - op.OperationID = typeNamePrefix(op.OperationID) + op.OperationID // These are parameters defined for the specific path method that // we're iterating over. - localParams, err := DescribeParameters(op.Parameters, []string{op.OperationID + "Params"}) + localParams, err := DescribeParameters(op.Parameters, []string{operationId + "Params"}) if err != nil { return nil, fmt.Errorf("error describing global parameters for %s/%s: %s", opName, requestPath, err) } // All the parameters required by a handler are the union of the // global parameters and the local parameters. - allParams := append(globalParams, localParams...) + allParams, err := CombineOperationParameters(globalParams, localParams) + if err != nil { + return nil, err + } + + ensureExternalRefsInParameterDefinitions(&allParams, pathItem.Ref) // Order the path parameters to match the order as specified in // the path, not in the swagger spec, and validate that the parameter @@ -535,22 +679,26 @@ func OperationDefinitions(swagger *openapi3.T) ([]OperationDefinition, error) { return nil, err } - bodyDefinitions, typeDefinitions, err := GenerateBodyDefinitions(op.OperationID, op.RequestBody) + bodyDefinitions, typeDefinitions, err := GenerateBodyDefinitions(operationId, op.RequestBody) if err != nil { return nil, fmt.Errorf("error generating body definitions: %w", err) } - responseDefinitions, err := GenerateResponseDefinitions(op.OperationID, op.Responses) + ensureExternalRefsInRequestBodyDefinitions(&bodyDefinitions, pathItem.Ref) + + responseDefinitions, err := GenerateResponseDefinitions(operationId, op.Responses.Map()) if err != nil { return nil, fmt.Errorf("error generating response definitions: %w", err) } + ensureExternalRefsInResponseDefinitions(&responseDefinitions, pathItem.Ref) + opDef := OperationDefinition{ PathParams: pathParams, HeaderParams: FilterParameterDefinitionByType(allParams, "header"), QueryParams: FilterParameterDefinitionByType(allParams, "query"), CookieParams: FilterParameterDefinitionByType(allParams, "cookie"), - OperationId: ToCamelCase(op.OperationID), + OperationId: nameNormalizer(operationId), // Replace newlines in summary. Summary: op.Summary, Method: opName, @@ -588,7 +736,7 @@ func OperationDefinitions(swagger *openapi3.T) ([]OperationDefinition, error) { return operations, nil } -func generateDefaultOperationID(opName string, requestPath string) (string, error) { +func generateDefaultOperationID(opName string, requestPath string, toCamelCaseFunc func(string) string) (string, error) { var operationId = strings.ToLower(opName) if opName == "" { @@ -605,7 +753,7 @@ func generateDefaultOperationID(opName string, requestPath string) (string, erro } } - return ToCamelCase(operationId), nil + return nameNormalizer(operationId), nil } // GenerateBodyDefinitions turns the Swagger body definitions into a list of our body @@ -619,15 +767,17 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody var bodyDefinitions []RequestBodyDefinition var typeDefinitions []TypeDefinition - for _, contentType := range SortedContentKeys(body.Content) { + for _, contentType := range SortedMapKeys(body.Content) { content := body.Content[contentType] var tag string var defaultBody bool switch { - case util.IsMediaTypeJson(contentType): + case contentType == "application/json": tag = "JSON" defaultBody = true + case util.IsMediaTypeJson(contentType): + tag = mediaTypeToCamelCase(contentType) case strings.HasPrefix(contentType, "multipart/"): tag = "Multipart" case contentType == "application/x-www-form-urlencoded": @@ -650,7 +800,7 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody } // If the body is a pre-defined type - if IsGoTypeReference(content.Schema.Ref) { + if content.Schema != nil && IsGoTypeReference(content.Schema.Ref) { // Convert the reference path to Go type refType, err := RefPathToGoType(content.Schema.Ref) if err != nil { @@ -663,6 +813,17 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody // type under #/components, we'll define a type for it, so // that we have an easy to use type for marshaling. if bodySchema.RefType == "" { + if contentType == "application/x-www-form-urlencoded" { + // Apply the appropriate structure tag if the request + // schema was defined under the operations' section. + for i := range bodySchema.Properties { + bodySchema.Properties[i].NeedsFormTag = true + } + + // Regenerate the Golang struct adding the new form tag. + bodySchema.GoType = GenStructFromSchema(bodySchema) + } + td := TypeDefinition{ TypeName: bodyTypeName, Schema: bodySchema, @@ -696,12 +857,12 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody return bodyDefinitions, typeDefinitions, nil } -func GenerateResponseDefinitions(operationID string, responses openapi3.Responses) ([]ResponseDefinition, error) { +func GenerateResponseDefinitions(operationID string, responses map[string]*openapi3.ResponseRef) ([]ResponseDefinition, error) { var responseDefinitions []ResponseDefinition // do not let multiple status codes ref to same response, it will break the type switch refSet := make(map[string]struct{}) - for _, statusCode := range SortedResponsesKeys(responses) { + for _, statusCode := range SortedMapKeys(responses) { responseOrRef := responses[statusCode] if responseOrRef == nil { continue @@ -710,12 +871,14 @@ func GenerateResponseDefinitions(operationID string, responses openapi3.Response var responseContentDefinitions []ResponseContentDefinition - for _, contentType := range SortedContentKeys(response.Content) { + for _, contentType := range SortedMapKeys(response.Content) { content := response.Content[contentType] var tag string switch { - case util.IsMediaTypeJson(contentType): + case contentType == "application/json": tag = "JSON" + case util.IsMediaTypeJson(contentType): + tag = mediaTypeToCamelCase(contentType) case contentType == "application/x-www-form-urlencoded": tag = "Formdata" case strings.HasPrefix(contentType, "multipart/"): @@ -741,11 +904,12 @@ func GenerateResponseDefinitions(operationID string, responses openapi3.Response NameTag: tag, Schema: contentSchema, } + responseContentDefinitions = append(responseContentDefinitions, rcd) } var responseHeaderDefinitions []ResponseHeaderDefinition - for _, headerName := range SortedHeadersKeys(response.Headers) { + for _, headerName := range SortedMapKeys(response.Headers) { header := response.Headers[headerName] contentSchema, err := GenerateGoSchema(header.Value.Schema, []string{}) if err != nil { @@ -776,6 +940,12 @@ func GenerateResponseDefinitions(operationID string, responses openapi3.Response rd.Ref = refType refSet[refType] = struct{}{} } + // Ensure content schemas get the external ref qualifier so that + // non-fixed status code paths (e.g. "default") emit the qualified type. + for i, rcd := range rd.Contents { + ensureExternalRefsInSchema(&rcd.Schema, responseOrRef.Ref) + rd.Contents[i] = rcd + } } responseDefinitions = append(responseDefinitions, rd) } @@ -792,11 +962,11 @@ func GenerateTypeDefsForOperation(op OperationDefinition) []TypeDefinition { // Now, go through all the additional types we need to declare. for _, param := range op.AllParams() { - typeDefs = append(typeDefs, param.Schema.GetAdditionalTypeDefs()...) + typeDefs = append(typeDefs, param.Schema.AdditionalTypes...) } for _, body := range op.Bodies { - typeDefs = append(typeDefs, body.Schema.GetAdditionalTypeDefs()...) + typeDefs = append(typeDefs, body.Schema.AdditionalTypes...) } return typeDefs } @@ -824,13 +994,24 @@ func GenerateParamsTypes(op OperationDefinition) []TypeDefinition { Schema: param.Schema, }) } + // Merge extensions from the schema level and the parameter level. + // Parameter-level extensions take precedence over schema-level ones. + extensions := make(map[string]any) + if param.Spec.Schema != nil && param.Spec.Schema.Value != nil { + for k, v := range param.Spec.Schema.Value.Extensions { + extensions[k] = v + } + } + for k, v := range param.Spec.Extensions { + extensions[k] = v + } prop := Property{ Description: param.Spec.Description, JsonFieldName: param.ParamName, Required: param.Required, Schema: pSchema, NeedsFormTag: param.Style() == "form", - Extensions: param.Spec.Extensions, + Extensions: extensions, } s.Properties = append(s.Properties, prop) } @@ -856,7 +1037,6 @@ func GenerateTypesForOperations(t *template.Template, ops []OperationDefinition) } if _, err := w.WriteString(addTypes); err != nil { return "", fmt.Errorf("error writing boilerplate to buffer: %w", err) - } // Generate boiler plate for all additional types. @@ -885,18 +1065,36 @@ func GenerateTypesForOperations(t *template.Template, ops []OperationDefinition) return buf.String(), nil } -// GenerateChiServer This function generates all the go code for the ServerInterface as well as +// GenerateIrisServer generates all the go code for the ServerInterface as well as +// all the wrapper functions around our handlers. +func GenerateIrisServer(t *template.Template, operations []OperationDefinition) (string, error) { + return GenerateTemplates([]string{"iris/iris-interface.tmpl", "iris/iris-middleware.tmpl", "iris/iris-handler.tmpl"}, t, operations) +} + +// GenerateChiServer generates all the go code for the ServerInterface as well as // all the wrapper functions around our handlers. func GenerateChiServer(t *template.Template, operations []OperationDefinition) (string, error) { return GenerateTemplates([]string{"chi/chi-interface.tmpl", "chi/chi-middleware.tmpl", "chi/chi-handler.tmpl"}, t, operations) } -// GenerateEchoServer This function generates all the go code for the ServerInterface as well as +// GenerateFiberServer generates all the go code for the ServerInterface as well as +// all the wrapper functions around our handlers. +func GenerateFiberServer(t *template.Template, operations []OperationDefinition) (string, error) { + return GenerateTemplates([]string{"fiber/fiber-interface.tmpl", "fiber/fiber-middleware.tmpl", "fiber/fiber-handler.tmpl"}, t, operations) +} + +// GenerateEchoServer generates all the go code for the ServerInterface as well as // all the wrapper functions around our handlers. func GenerateEchoServer(t *template.Template, operations []OperationDefinition) (string, error) { return GenerateTemplates([]string{"echo/echo-interface.tmpl", "echo/echo-wrappers.tmpl", "echo/echo-register.tmpl"}, t, operations) } +// GenerateEcho5Server generates all the go code for the ServerInterface as well as +// all the wrapper functions around our handlers. +func GenerateEcho5Server(t *template.Template, operations []OperationDefinition) (string, error) { + return GenerateTemplates([]string{"echo/v5/echo-interface.tmpl", "echo/v5/echo-wrappers.tmpl", "echo/v5/echo-register.tmpl"}, t, operations) +} + // GenerateGinServer generates all the go code for the ServerInterface as well as // all the wrapper functions around our handlers. func GenerateGinServer(t *template.Template, operations []OperationDefinition) (string, error) { @@ -909,17 +1107,35 @@ func GenerateGorillaServer(t *template.Template, operations []OperationDefinitio return GenerateTemplates([]string{"gorilla/gorilla-interface.tmpl", "gorilla/gorilla-middleware.tmpl", "gorilla/gorilla-register.tmpl"}, t, operations) } +// GenerateStdHTTPServer generates all the go code for the ServerInterface as well as +// all the wrapper functions around our handlers. +func GenerateStdHTTPServer(t *template.Template, operations []OperationDefinition) (string, error) { + return GenerateTemplates([]string{"stdhttp/std-http-interface.tmpl", "stdhttp/std-http-middleware.tmpl", "stdhttp/std-http-handler.tmpl"}, t, operations) +} + func GenerateStrictServer(t *template.Template, operations []OperationDefinition, opts Configuration) (string, error) { - templates := []string{"strict/strict-interface.tmpl"} - if opts.Generate.ChiServer || opts.Generate.GorillaServer { - templates = append(templates, "strict/strict-http.tmpl") + + var templates []string + + if opts.Generate.ChiServer || opts.Generate.GorillaServer || opts.Generate.StdHTTPServer { + templates = append(templates, "strict/strict-interface.tmpl", "strict/strict-http.tmpl") } if opts.Generate.EchoServer { - templates = append(templates, "strict/strict-echo.tmpl") + templates = append(templates, "strict/strict-interface.tmpl", "strict/strict-echo.tmpl") } if opts.Generate.GinServer { - templates = append(templates, "strict/strict-gin.tmpl") + templates = append(templates, "strict/strict-interface.tmpl", "strict/strict-gin.tmpl") + } + if opts.Generate.FiberServer { + templates = append(templates, "strict/strict-fiber-interface.tmpl", "strict/strict-fiber.tmpl") + } + if opts.Generate.IrisServer { + templates = append(templates, "strict/strict-iris-interface.tmpl", "strict/strict-iris.tmpl") + } + if opts.Generate.Echo5Server { + templates = append(templates, "strict/strict-interface.tmpl", "strict/strict-echo5.tmpl") } + return GenerateTemplates(templates, t, operations) } @@ -934,13 +1150,13 @@ func GenerateClient(t *template.Template, ops []OperationDefinition) (string, er } // GenerateClientWithResponses generates a client which extends the basic client which does response -// unmarshalling. +// unmarshaling. func GenerateClientWithResponses(t *template.Template, ops []OperationDefinition) (string, error) { return GenerateTemplates([]string{"client-with-responses.tmpl"}, t, ops) } // GenerateTemplates used to generate templates -func GenerateTemplates(templates []string, t *template.Template, ops interface{}) (string, error) { +func GenerateTemplates(templates []string, t *template.Template, ops any) (string, error) { var generatedTemplates []string for _, tmpl := range templates { var buf bytes.Buffer @@ -957,3 +1173,33 @@ func GenerateTemplates(templates []string, t *template.Template, ops interface{} return strings.Join(generatedTemplates, "\n"), nil } + +// CombineOperationParameters combines the Parameters defined at a global level (Parameters defined for all methods on a given path) with the Parameters defined at a local level (Parameters defined for a specific path), preferring the locally defined parameter over the global one +func CombineOperationParameters(globalParams []ParameterDefinition, localParams []ParameterDefinition) ([]ParameterDefinition, error) { + allParams := make([]ParameterDefinition, 0, len(globalParams)+len(localParams)) + dupCheck := make(map[string]map[string]string) + for _, p := range localParams { + if dupCheck[p.In] == nil { + dupCheck[p.In] = make(map[string]string) + } + if _, exist := dupCheck[p.In][p.ParamName]; !exist { + dupCheck[p.In][p.ParamName] = "local" + allParams = append(allParams, p) + } else { + return nil, fmt.Errorf("duplicate local parameter %s/%s", p.In, p.ParamName) + } + } + for _, p := range globalParams { + if dupCheck[p.In] == nil { + dupCheck[p.In] = make(map[string]string) + } + if t, exist := dupCheck[p.In][p.ParamName]; !exist { + dupCheck[p.In][p.ParamName] = "global" + allParams = append(allParams, p) + } else if t == "global" { + return nil, fmt.Errorf("duplicate global parameter %s/%s", p.In, p.ParamName) + } + } + + return allParams, nil +} diff --git a/pkg/codegen/operations_test.go b/pkg/codegen/operations_test.go index 0eff03e2c2..77a54a8c25 100644 --- a/pkg/codegen/operations_test.go +++ b/pkg/codegen/operations_test.go @@ -131,7 +131,7 @@ func TestGenerateDefaultOperationID(t *testing.T) { } for _, test := range suite { - got, err := generateDefaultOperationID(test.op, test.path) + got, err := generateDefaultOperationID(test.op, test.path, ToCamelCase) if err != nil { if !test.wantErr { t.Fatalf("did not expected error but got %v", err) diff --git a/pkg/codegen/prune.go b/pkg/codegen/prune.go index 9dc4fd8719..e97ba3469e 100644 --- a/pkg/codegen/prune.go +++ b/pkg/codegen/prune.go @@ -2,31 +2,27 @@ package codegen import ( "fmt" + "slices" "github.com/getkin/kin-openapi/openapi3" ) func stringInSlice(a string, list []string) bool { - for _, b := range list { - if b == a { - return true - } - } - return false + return slices.Contains(list, a) } type RefWrapper struct { Ref string HasValue bool - SourceRef interface{} + SourceRef any } func walkSwagger(swagger *openapi3.T, doFn func(RefWrapper) (bool, error)) error { - if swagger == nil { + if swagger == nil || swagger.Paths == nil { return nil } - for _, p := range swagger.Paths { + for _, p := range swagger.Paths.Map() { for _, param := range p.Parameters { _ = walkParameterRef(param, doFn) } @@ -52,8 +48,10 @@ func walkOperation(op *openapi3.Operation, doFn func(RefWrapper) (bool, error)) _ = walkRequestBodyRef(op.RequestBody, doFn) - for _, response := range op.Responses { - _ = walkResponseRef(response, doFn) + if op.Responses != nil { + for _, response := range op.Responses.Map() { + _ = walkResponseRef(response, doFn) + } } for _, callback := range op.Callbacks { @@ -273,7 +271,7 @@ func walkCallbackRef(ref *openapi3.CallbackRef, doFn func(RefWrapper) (bool, err return nil } - for _, pathItem := range *ref.Value { + for _, pathItem := range ref.Value.Map() { for _, parameter := range pathItem.Parameters { _ = walkParameterRef(parameter, doFn) } diff --git a/pkg/codegen/prune_test.go b/pkg/codegen/prune_test.go index e0cc16c08a..9972f5bac7 100644 --- a/pkg/codegen/prune_test.go +++ b/pkg/codegen/prune_test.go @@ -67,9 +67,9 @@ func TestFilterOnlyCat(t *testing.T) { refs = findComponentRefs(swagger) assert.Len(t, refs, 7) - assert.NotEmpty(t, swagger.Paths["/cat"], "/cat path should still be in spec") - assert.NotEmpty(t, swagger.Paths["/cat"].Get, "GET /cat operation should still be in spec") - assert.Empty(t, swagger.Paths["/dog"].Get, "GET /dog should have been removed from spec") + assert.NotEmpty(t, swagger.Paths.Value("/cat"), "/cat path should still be in spec") + assert.NotEmpty(t, swagger.Paths.Value("/cat").Get, "GET /cat operation should still be in spec") + assert.Empty(t, swagger.Paths.Value("/dog").Get, "GET /dog should have been removed from spec") pruneUnusedComponents(swagger) @@ -97,9 +97,9 @@ func TestFilterOnlyDog(t *testing.T) { assert.Len(t, swagger.Components.Schemas, 5) - assert.NotEmpty(t, swagger.Paths["/dog"]) - assert.NotEmpty(t, swagger.Paths["/dog"].Get) - assert.Empty(t, swagger.Paths["/cat"].Get) + assert.NotEmpty(t, swagger.Paths.Value("/dog")) + assert.NotEmpty(t, swagger.Paths.Value("/dog").Get) + assert.Empty(t, swagger.Paths.Value("/cat").Get) pruneUnusedComponents(swagger) diff --git a/pkg/codegen/resolve_names.go b/pkg/codegen/resolve_names.go new file mode 100644 index 0000000000..f6d65f5b92 --- /dev/null +++ b/pkg/codegen/resolve_names.go @@ -0,0 +1,339 @@ +package codegen + +import ( + "fmt" + "sort" + "strconv" + "strings" +) + +// ResolvedName holds the final Go type name assigned to a gathered schema. +type ResolvedName struct { + Schema *GatheredSchema + GoName string // The resolved Go type name + Candidate string // The initial candidate name before collision resolution + Pinned bool // True if name came from x-go-name; must not be renamed +} + +// ResolveNames takes the gathered schemas and assigns unique Go type names to each. +// It returns a map from the schema's path string to the resolved Go type name. +func ResolveNames(schemas []*GatheredSchema) map[string]string { + // Step 1: Generate candidate names for all schemas + candidates := make([]*ResolvedName, len(schemas)) + for i, s := range schemas { + candidate := generateCandidateName(s) + candidates[i] = &ResolvedName{ + Schema: s, + GoName: candidate, + Candidate: candidate, + Pinned: s.GoNameOverride != "", + } + } + + // Step 2: Resolve collisions iteratively + resolveCollisions(candidates) + + // Step 3: Build the result map + result := make(map[string]string, len(candidates)) + for _, c := range candidates { + result[c.Schema.Path.String()] = c.GoName + } + return result +} + +// generateCandidateName produces an initial Go type name candidate based on +// the schema's location and context in the OpenAPI document. +func generateCandidateName(s *GatheredSchema) string { + if s.GoNameOverride != "" { + return s.GoNameOverride + } + + switch s.Context { + case ContextComponentSchema: + return SchemaNameToTypeName(s.ComponentName) + + case ContextComponentParameter: + return SchemaNameToTypeName(s.ComponentName) + + case ContextComponentResponse: + return SchemaNameToTypeName(s.ComponentName) + + case ContextComponentRequestBody: + return SchemaNameToTypeName(s.ComponentName) + + case ContextComponentHeader: + return SchemaNameToTypeName(s.ComponentName) + + case ContextClientResponseWrapper: + // Client response wrappers use: OperationId + responseTypeSuffix + return fmt.Sprintf("%s%s", SchemaNameToTypeName(s.OperationID), responseTypeSuffix) + + case ContextOperationParameter: + if s.OperationID != "" { + return SchemaNameToTypeName(s.OperationID) + "Parameter" + } + return SchemaNameToTypeName(s.ComponentName) + "Parameter" + + case ContextOperationRequestBody: + if s.OperationID != "" { + ct := contentTypeSuffix(s.ContentType) + return SchemaNameToTypeName(s.OperationID) + ct + "Request" + } + return SchemaNameToTypeName(s.ComponentName) + "Request" + + case ContextOperationResponse: + if s.OperationID != "" { + ct := contentTypeSuffix(s.ContentType) + return SchemaNameToTypeName(s.OperationID) + s.StatusCode + ct + "Response" + } + return SchemaNameToTypeName(s.ComponentName) + "Response" + + default: + return SchemaNameToTypeName(s.ComponentName) + } +} + +// resolveCollisions detects and resolves naming collisions among the resolved names. +// It applies strategies in global phases of increasing aggressiveness: +// 1. Context suffix (Schema, Parameter, Response, etc.) +// 2. Per-schema disambiguation (content type, status code, etc.) +// 3. Numeric fallback +// +// Each strategy is applied to ALL colliding groups, then collisions are +// re-checked globally before moving to the next strategy. This prevents +// oscillation between strategies (e.g., context suffix and content type +// suffix repeatedly appending to the same names without resolution). +func resolveCollisions(names []*ResolvedName) { + strategies := []func([]*ResolvedName) bool{ + strategyContextSuffix, + strategyPerSchemaDisambiguate, + strategyNumericFallback, + } + + const maxIterations = 20 + + for _, strategy := range strategies { + for iter := 0; iter < maxIterations; iter++ { + groups := groupByName(names) + anyCollision := false + anyProgress := false + for _, group := range groups { + if len(group) <= 1 { + continue + } + anyCollision = true + if strategy(group) { + anyProgress = true + } + } + if !anyCollision { + return + } + if !anyProgress { + break // This strategy can't help; try the next one + } + } + } +} + +// groupByName groups ResolvedNames by their current GoName. +func groupByName(names []*ResolvedName) map[string][]*ResolvedName { + groups := make(map[string][]*ResolvedName) + for _, n := range names { + groups[n.GoName] = append(groups[n.GoName], n) + } + return groups +} + +// strategyContextSuffix attempts to resolve collisions by appending a suffix +// derived from the schema's context (Schema, Parameter, Response, etc.). +// Component schemas are "privileged" — if exactly one member is a component +// schema, it keeps the bare name and only the others get suffixed. +// Returns true if any name was modified, false if no progress was made. +func strategyContextSuffix(group []*ResolvedName) bool { + // Count how many are component schemas (privileged) + var componentSchemaCount int + for _, n := range group { + if n.Schema.IsComponentSchema() { + componentSchemaCount++ + } + } + + progress := false + for _, n := range group { + if n.Pinned { + continue + } + + suffix := n.Schema.Context.Suffix() + if suffix == "" { + continue + } + + // If exactly one is a component schema, it keeps the bare name + if componentSchemaCount == 1 && n.Schema.IsComponentSchema() { + continue + } + + // Don't add suffix if name already ends with it + if strings.HasSuffix(n.GoName, suffix) { + continue + } + + n.GoName = n.GoName + suffix + progress = true + } + return progress +} + +// strategyPerSchemaDisambiguate tries several per-schema disambiguation strategies. +// Returns true if any name was modified, false if no progress was made. +func strategyPerSchemaDisambiguate(group []*ResolvedName) bool { + progress := tryContentTypeSuffix(group) + if !progress && tryStatusCodeSuffix(group) { + progress = true + } + if !progress && tryParamIndexSuffix(group) { + progress = true + } + return progress +} + +// tryContentTypeSuffix appends a content type discriminator when schemas +// differ by media type (e.g., JSON vs XML). +// Returns true if any name was modified, false if no progress was made. +func tryContentTypeSuffix(group []*ResolvedName) bool { + // Check if any members have different content types + contentTypes := make(map[string]bool) + for _, n := range group { + if n.Schema.ContentType != "" { + contentTypes[n.Schema.ContentType] = true + } + } + if len(contentTypes) <= 1 { + return false + } + + progress := false + for _, n := range group { + if n.Pinned { + continue + } + if n.Schema.ContentType == "" { + continue + } + suffix := contentTypeSuffix(n.Schema.ContentType) + if suffix != "" && !strings.HasSuffix(n.GoName, suffix) { + n.GoName = n.GoName + suffix + progress = true + } + } + return progress +} + +// tryStatusCodeSuffix appends the HTTP status code when schemas differ by status. +// Returns true if any name was modified, false if no progress was made. +func tryStatusCodeSuffix(group []*ResolvedName) bool { + statusCodes := make(map[string]bool) + for _, n := range group { + if n.Schema.StatusCode != "" { + statusCodes[n.Schema.StatusCode] = true + } + } + if len(statusCodes) <= 1 { + return false + } + + progress := false + for _, n := range group { + if n.Pinned { + continue + } + if n.Schema.StatusCode != "" && !strings.HasSuffix(n.GoName, n.Schema.StatusCode) { + n.GoName = n.GoName + n.Schema.StatusCode + progress = true + } + } + return progress +} + +// tryParamIndexSuffix appends a parameter index when schemas differ by position. +// Returns true if any name was modified, false if no progress was made. +func tryParamIndexSuffix(group []*ResolvedName) bool { + hasMultipleParams := false + for i := 0; i < len(group); i++ { + for j := i + 1; j < len(group); j++ { + if group[i].Schema.ParamIndex != group[j].Schema.ParamIndex { + hasMultipleParams = true + break + } + } + if hasMultipleParams { + break + } + } + if !hasMultipleParams { + return false + } + + progress := false + for _, n := range group { + if n.Pinned { + continue + } + suffix := strconv.Itoa(n.Schema.ParamIndex) + if !strings.HasSuffix(n.GoName, suffix) { + n.GoName = n.GoName + suffix + progress = true + } + } + return progress +} + +// strategyNumericFallback is the last resort: append increasing numbers. +// Returns true if any name was modified (always true when group has 2+ members). +func strategyNumericFallback(group []*ResolvedName) bool { + // Sort for determinism: pinned first, then component schemas, then by path + sort.Slice(group, func(i, j int) bool { + if group[i].Pinned != group[j].Pinned { + return group[i].Pinned + } + if group[i].Schema.IsComponentSchema() != group[j].Schema.IsComponentSchema() { + return group[i].Schema.IsComponentSchema() + } + return group[i].Schema.Path.String() < group[j].Schema.Path.String() + }) + + // First non-pinned keeps name, rest get numeric suffix + for i := 1; i < len(group); i++ { + if group[i].Pinned { + continue + } + group[i].GoName = group[i].GoName + strconv.Itoa(i+1) + } + return len(group) > 1 +} + +// contentTypeSuffix returns a short suffix for a media type. +func contentTypeSuffix(ct string) string { + if ct == "" { + return "" + } + ct = strings.ToLower(ct) + switch { + case strings.Contains(ct, "json"): + return "JSON" + case strings.Contains(ct, "xml"): + return "XML" + case strings.Contains(ct, "form"): + return "Form" + case strings.Contains(ct, "text"): + return "Text" + case strings.Contains(ct, "octet"): + return "Binary" + case strings.Contains(ct, "yaml"): + return "YAML" + default: + return mediaTypeToCamelCase(ct) + } +} diff --git a/pkg/codegen/resolve_names_test.go b/pkg/codegen/resolve_names_test.go new file mode 100644 index 0000000000..806eaf6740 --- /dev/null +++ b/pkg/codegen/resolve_names_test.go @@ -0,0 +1,379 @@ +package codegen + +import ( + "testing" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/stretchr/testify/assert" +) + +func TestResolveNames_NoCollisions(t *testing.T) { + schemas := []*GatheredSchema{ + { + Path: SchemaPath{"components", "schemas", "Pet"}, + Context: ContextComponentSchema, + Schema: &openapi3.Schema{}, + ComponentName: "Pet", + }, + { + Path: SchemaPath{"components", "schemas", "Owner"}, + Context: ContextComponentSchema, + Schema: &openapi3.Schema{}, + ComponentName: "Owner", + }, + } + + result := ResolveNames(schemas) + + assert.Equal(t, "Pet", result["components/schemas/Pet"]) + assert.Equal(t, "Owner", result["components/schemas/Owner"]) +} + +func TestResolveNames_Issue200_CrossSectionCollisions(t *testing.T) { + // "Bar" appears in schemas, parameters, responses, requestBodies, headers + schemas := []*GatheredSchema{ + { + Path: SchemaPath{"components", "schemas", "Bar"}, + Context: ContextComponentSchema, + Schema: &openapi3.Schema{}, + ComponentName: "Bar", + }, + { + Path: SchemaPath{"components", "parameters", "Bar"}, + Context: ContextComponentParameter, + Schema: &openapi3.Schema{}, + ComponentName: "Bar", + }, + { + Path: SchemaPath{"components", "responses", "Bar", "content", "application/json"}, + Context: ContextComponentResponse, + Schema: &openapi3.Schema{}, + ComponentName: "Bar", + ContentType: "application/json", + }, + { + Path: SchemaPath{"components", "requestBodies", "Bar", "content", "application/json"}, + Context: ContextComponentRequestBody, + Schema: &openapi3.Schema{}, + ComponentName: "Bar", + ContentType: "application/json", + }, + { + Path: SchemaPath{"components", "headers", "Bar"}, + Context: ContextComponentHeader, + Schema: &openapi3.Schema{}, + ComponentName: "Bar", + }, + } + + result := ResolveNames(schemas) + + // Component schema is privileged — keeps bare name + assert.Equal(t, "Bar", result["components/schemas/Bar"]) + // Others get context suffixes + assert.Equal(t, "BarParameter", result["components/parameters/Bar"]) + assert.Equal(t, "BarResponse", result["components/responses/Bar/content/application/json"]) + assert.Equal(t, "BarRequestBody", result["components/requestBodies/Bar/content/application/json"]) + assert.Equal(t, "BarHeader", result["components/headers/Bar"]) +} + +func TestResolveNames_Issue1474_ClientWrapperCollision(t *testing.T) { + // Schema named "CreateChatCompletionResponse" collides with + // client wrapper for operation "createChatCompletion" which + // would generate "CreateChatCompletionResponse". + schemas := []*GatheredSchema{ + { + Path: SchemaPath{"components", "schemas", "CreateChatCompletionResponse"}, + Context: ContextComponentSchema, + Schema: &openapi3.Schema{}, + ComponentName: "CreateChatCompletionResponse", + }, + { + Path: SchemaPath{"paths", "/chat/completions", "POST", "x-client-response-wrapper"}, + Context: ContextClientResponseWrapper, + OperationID: "createChatCompletion", + }, + } + + result := ResolveNames(schemas) + + // Component schema is privileged — keeps its name + assert.Equal(t, "CreateChatCompletionResponse", result["components/schemas/CreateChatCompletionResponse"]) + // Client wrapper gets a suffix to avoid collision + wrapperName := result["paths//chat/completions/POST/x-client-response-wrapper"] + assert.NotEqual(t, "CreateChatCompletionResponse", wrapperName, + "client wrapper should not collide with component schema") + assert.Contains(t, wrapperName, "Response", + "client wrapper should still contain 'Response'") +} + +func TestResolveNames_PrivilegedComponentSchema(t *testing.T) { + // When exactly one collision member is a component schema, + // it keeps the bare name + schemas := []*GatheredSchema{ + { + Path: SchemaPath{"components", "schemas", "Foo"}, + Context: ContextComponentSchema, + Schema: &openapi3.Schema{}, + ComponentName: "Foo", + }, + { + Path: SchemaPath{"components", "parameters", "Foo"}, + Context: ContextComponentParameter, + Schema: &openapi3.Schema{}, + ComponentName: "Foo", + }, + } + + result := ResolveNames(schemas) + + assert.Equal(t, "Foo", result["components/schemas/Foo"]) + assert.Equal(t, "FooParameter", result["components/parameters/Foo"]) +} + +func TestResolveNames_NoComponentSchema_AllGetSuffixes(t *testing.T) { + // When no member is a component schema, all get suffixed + schemas := []*GatheredSchema{ + { + Path: SchemaPath{"components", "parameters", "Foo"}, + Context: ContextComponentParameter, + Schema: &openapi3.Schema{}, + ComponentName: "Foo", + }, + { + Path: SchemaPath{"components", "responses", "Foo", "content", "application/json"}, + Context: ContextComponentResponse, + Schema: &openapi3.Schema{}, + ComponentName: "Foo", + ContentType: "application/json", + }, + } + + result := ResolveNames(schemas) + + assert.Equal(t, "FooParameter", result["components/parameters/Foo"]) + assert.Equal(t, "FooResponse", result["components/responses/Foo/content/application/json"]) +} + +func TestResolveNames_NumericFallback(t *testing.T) { + // Two schemas with same context that can't be disambiguated + // by context suffix (both are component schemas) + schemas := []*GatheredSchema{ + { + Path: SchemaPath{"components", "schemas", "Foo"}, + Context: ContextComponentSchema, + Schema: &openapi3.Schema{}, + ComponentName: "Foo", + }, + { + // Hypothetical: same candidate name from a different path + // This shouldn't normally happen with real specs, but tests the fallback + Path: SchemaPath{"components", "schemas", "foo"}, + Context: ContextComponentSchema, + Schema: &openapi3.Schema{}, + ComponentName: "foo", + }, + } + + result := ResolveNames(schemas) + + names := make(map[string]bool) + for _, name := range result { + names[name] = true + } + // Both should have unique names + assert.Len(t, names, 2, "should have two unique names") +} + +func TestResolveNames_MultipleJsonContentTypes(t *testing.T) { + // "Order" appears in schemas and requestBodies. The requestBody has + // 3 content types that all contain "json" and map to the same "JSON" + // suffix. The global phase approach should prevent oscillation between + // context suffix and content type suffix, letting numeric fallback resolve. + schemas := []*GatheredSchema{ + { + Path: SchemaPath{"components", "schemas", "Order"}, + Context: ContextComponentSchema, + Schema: &openapi3.Schema{}, + ComponentName: "Order", + }, + { + Path: SchemaPath{"components", "requestBodies", "Order", "content", "application/json"}, + Context: ContextComponentRequestBody, + Schema: &openapi3.Schema{}, + ComponentName: "Order", + ContentType: "application/json", + }, + { + Path: SchemaPath{"components", "requestBodies", "Order", "content", "application/merge-patch+json"}, + Context: ContextComponentRequestBody, + Schema: &openapi3.Schema{}, + ComponentName: "Order", + ContentType: "application/merge-patch+json", + }, + { + Path: SchemaPath{"components", "requestBodies", "Order", "content", "application/json-patch+json"}, + Context: ContextComponentRequestBody, + Schema: &openapi3.Schema{}, + ComponentName: "Order", + ContentType: "application/json-patch+json", + }, + } + + result := ResolveNames(schemas) + + // Component schema keeps bare name + assert.Equal(t, "Order", result["components/schemas/Order"]) + + // All 3 requestBody types must have unique names + names := make(map[string]bool) + for _, name := range result { + names[name] = true + } + assert.Len(t, names, 4, "all 4 types should have unique names") + + // The first requestBody should get RequestBody+JSON suffixes + assert.Equal(t, "OrderRequestBodyJSON", + result["components/requestBodies/Order/content/application/json"]) + + // The remaining two collide on OrderRequestBodyJSON and get numeric fallback + jsonPatchName := result["components/requestBodies/Order/content/application/json-patch+json"] + mergePatchName := result["components/requestBodies/Order/content/application/merge-patch+json"] + assert.NotEqual(t, jsonPatchName, mergePatchName, + "json-patch and merge-patch types must have different names") + assert.Contains(t, jsonPatchName, "OrderRequestBodyJSON") + assert.Contains(t, mergePatchName, "OrderRequestBodyJSON") +} + +func TestResolveNames_XGoNamePinned_Schema(t *testing.T) { + // Pattern K: schema "Renamer" has x-go-name="SpecialName" which collides + // with nothing, but a response also named "Renamer" should keep its + // normal resolved name. The schema is pinned. + schemas := []*GatheredSchema{ + { + Path: SchemaPath{"components", "schemas", "Renamer"}, + Context: ContextComponentSchema, + Schema: &openapi3.Schema{}, + ComponentName: "Renamer", + GoNameOverride: "SpecialName", + }, + { + Path: SchemaPath{"components", "responses", "Renamer", "content", "application/json"}, + Context: ContextComponentResponse, + Schema: &openapi3.Schema{}, + ComponentName: "Renamer", + ContentType: "application/json", + }, + } + + result := ResolveNames(schemas) + + // Schema pinned to SpecialName + assert.Equal(t, "SpecialName", result["components/schemas/Renamer"]) + // Response keeps bare name since there's no collision with "Renamer" + assert.Equal(t, "Renamer", result["components/responses/Renamer/content/application/json"]) +} + +func TestResolveNames_XGoNamePinned_Response(t *testing.T) { + // Pattern L: response "Outcome" has x-go-name="OutcomeResult" and + // schema also named "Outcome". The response is pinned as "OutcomeResult", + // so the schema keeps "Outcome" (no collision). + schemas := []*GatheredSchema{ + { + Path: SchemaPath{"components", "schemas", "Outcome"}, + Context: ContextComponentSchema, + Schema: &openapi3.Schema{}, + ComponentName: "Outcome", + }, + { + Path: SchemaPath{"components", "responses", "Outcome", "content", "application/json"}, + Context: ContextComponentResponse, + Schema: &openapi3.Schema{}, + ComponentName: "Outcome", + ContentType: "application/json", + GoNameOverride: "OutcomeResult", + }, + } + + result := ResolveNames(schemas) + + // Schema keeps bare name + assert.Equal(t, "Outcome", result["components/schemas/Outcome"]) + // Response pinned to OutcomeResult + assert.Equal(t, "OutcomeResult", result["components/responses/Outcome/content/application/json"]) +} + +func TestResolveNames_XGoNamePinned_RequestBody(t *testing.T) { + // Pattern M: requestBody "Payload" has x-go-name="PayloadBody" and + // schema also named "Payload". The requestBody is pinned. + schemas := []*GatheredSchema{ + { + Path: SchemaPath{"components", "schemas", "Payload"}, + Context: ContextComponentSchema, + Schema: &openapi3.Schema{}, + ComponentName: "Payload", + }, + { + Path: SchemaPath{"components", "requestBodies", "Payload", "content", "application/json"}, + Context: ContextComponentRequestBody, + Schema: &openapi3.Schema{}, + ComponentName: "Payload", + ContentType: "application/json", + GoNameOverride: "PayloadBody", + }, + } + + result := ResolveNames(schemas) + + // Schema keeps bare name + assert.Equal(t, "Payload", result["components/schemas/Payload"]) + // RequestBody pinned to PayloadBody + assert.Equal(t, "PayloadBody", result["components/requestBodies/Payload/content/application/json"]) +} + +func TestResolveNames_PinnedNotModifiedByStrategies(t *testing.T) { + // Pinned name "Foo" vs non-pinned parameter "Foo" → parameter gets suffixed + schemas := []*GatheredSchema{ + { + Path: SchemaPath{"components", "schemas", "Foo"}, + Context: ContextComponentSchema, + Schema: &openapi3.Schema{}, + ComponentName: "Foo", + GoNameOverride: "Foo", + }, + { + Path: SchemaPath{"components", "parameters", "Foo"}, + Context: ContextComponentParameter, + Schema: &openapi3.Schema{}, + ComponentName: "Foo", + }, + } + + result := ResolveNames(schemas) + + // Pinned schema stays as "Foo" + assert.Equal(t, "Foo", result["components/schemas/Foo"]) + // Parameter gets suffixed to resolve collision + assert.Equal(t, "FooParameter", result["components/parameters/Foo"]) +} + +func TestContentTypeSuffix(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"application/json", "JSON"}, + {"application/xml", "XML"}, + {"application/x-www-form-urlencoded", "Form"}, + {"text/plain", "Text"}, + {"application/octet-stream", "Binary"}, + {"application/yaml", "YAML"}, + {"", ""}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + assert.Equal(t, tt.expected, contentTypeSuffix(tt.input)) + }) + } +} diff --git a/pkg/codegen/schema.go b/pkg/codegen/schema.go index 1f10d4823e..ff72d23b6b 100644 --- a/pkg/codegen/schema.go +++ b/pkg/codegen/schema.go @@ -1,6 +1,7 @@ package codegen import ( + "errors" "fmt" "strings" @@ -32,16 +33,26 @@ type Schema struct { // If this is set, the schema will declare a type via alias, eg, // `type Foo = bool`. If this is not set, we will define this type via // type definition `type Foo bool` + // + // Can be overriden by the OutputOptions#DisableTypeAliasesForType field DefineViaAlias bool // The original OpenAPIv3 Schema. OAPISchema *openapi3.Schema + } func (s Schema) IsRef() bool { return s.RefType != "" } +func (s Schema) IsExternalRef() bool { + if !s.IsRef() { + return false + } + return strings.Contains(s.RefType, ".") +} + func (s Schema) TypeDecl() string { if s.IsRef() { return s.RefType @@ -52,7 +63,7 @@ func (s Schema) TypeDecl() string { // AddProperty adds a new property to the current Schema, and returns an error // if it collides. Two identical fields will not collide, but two properties by // the same name, but different definition, will collide. It's safe to merge the -// fields of two schemas with overalapping properties if those properties are +// fields of two schemas with overlapping properties if those properties are // identical. func (s *Schema) AddProperty(p Property) error { // Scan all existing properties for a conflict @@ -66,12 +77,7 @@ func (s *Schema) AddProperty(p Property) error { } func (s Schema) GetAdditionalTypeDefs() []TypeDefinition { - var result []TypeDefinition - for _, p := range s.Properties { - result = append(result, p.Schema.GetAdditionalTypeDefs()...) - } - result = append(result, s.AdditionalTypes...) - return result + return s.AdditionalTypes } type Property struct { @@ -83,15 +89,36 @@ type Property struct { ReadOnly bool WriteOnly bool NeedsFormTag bool - Extensions map[string]interface{} + Extensions map[string]any + Deprecated bool } func (p Property) GoFieldName() string { - return SchemaNameToTypeName(p.JsonFieldName) + goFieldName := p.JsonFieldName + if extension, ok := p.Extensions[extGoName]; ok { + if extGoFieldName, err := extParseGoFieldName(extension); err == nil { + goFieldName = extGoFieldName + } + } + + if globalState.options.Compatibility.AllowUnexportedStructFieldNames { + if extension, ok := p.Extensions[extOapiCodegenOnlyHonourGoName]; ok { + if extOapiCodegenOnlyHonourGoName, err := extParseOapiCodegenOnlyHonourGoName(extension); err == nil { + if extOapiCodegenOnlyHonourGoName { + return goFieldName + } + } + } + } + + return SchemaNameToTypeName(goFieldName) } func (p Property) GoTypeDef() string { typeDef := p.Schema.TypeDecl() + if globalState.options.OutputOptions.NullableType && p.Nullable { + return "nullable.Nullable[" + typeDef + "]" + } if !p.Schema.SkipOptionalPointer && (!p.Required || p.Nullable || (p.ReadOnly && (!p.Required || !globalState.options.Compatibility.DisableRequiredReadOnlyAsPointer)) || @@ -102,6 +129,29 @@ func (p Property) GoTypeDef() string { return typeDef } +// RequiresNilCheck indicates whether the generated property should have a nil check performed on it before other checks. +// This should be used in templates when performing `nil` checks, but NOT when i.e. determining if there should be an optional pointer given to the type - in that case, use `HasOptionalPointer` +func (p Property) RequiresNilCheck() bool { + return p.ZeroValueIsNil() || p.HasOptionalPointer() +} + +// HasOptionalPointer indicates whether the generated property has an optional pointer associated with it. +// This takes into account the `x-go-type-skip-optional-pointer` extension, allowing a parameter definition to control whether the pointer should be skipped. +func (p Property) HasOptionalPointer() bool { + return p.Required == false && p.Schema.SkipOptionalPointer == false //nolint:staticcheck +} + +// ZeroValueIsNil is a helper function to determine if the given Go type used for this property +// Will return true if the OpenAPI `type` is: +// - `array` +func (p Property) ZeroValueIsNil() bool { + if p.Schema.OAPISchema == nil { + return false + } + + return p.Schema.OAPISchema.Type.Is("array") +} + // EnumDefinition holds type information for enum type EnumDefinition struct { // Schema is the scheme of a type which has a list of enum values, eg, the @@ -165,7 +215,7 @@ type TypeDefinition struct { } // ResponseTypeDefinition is an extension of TypeDefinition, specifically for -// response unmarshalling in ClientWithResponses. +// response unmarshaling in ClientWithResponses. type ResponseTypeDefinition struct { TypeDefinition // The content type name where this is used, eg, application/json @@ -173,6 +223,8 @@ type ResponseTypeDefinition struct { // The type name of a response model. ResponseName string + + AdditionalTypeDefinitions []TypeDefinition } func (t *TypeDefinition) IsAlias() bool { @@ -205,11 +257,11 @@ func (u UnionElement) String() string { // Method generate union method name for template functions `As/From/Merge`. func (u UnionElement) Method() string { - var method string + var method strings.Builder for _, part := range strings.Split(string(u), `.`) { - method += UppercaseFirstCharacter(part) + method.WriteString(UppercaseFirstCharacter(part)) } - return method + return method.String() } func PropertiesEqual(a, b Property) bool { @@ -226,6 +278,18 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { schema := sref.Value + // Check x-go-type-skip-optional-pointer, which will override if the type + // should be a pointer or not when the field is optional. + // NOTE skipOptionalPointer will be defaulted to the global value, but can be overridden on a per-type/-field basis + skipOptionalPointer := globalState.options.OutputOptions.PreferSkipOptionalPointer + if extension, ok := schema.Extensions[extPropGoTypeSkipOptionalPointer]; ok { + var err error + skipOptionalPointer, err = extParsePropGoTypeSkipOptionalPointer(extension) + if err != nil { + return Schema{}, fmt.Errorf("invalid value for %q: %w", extPropGoTypeSkipOptionalPointer, err) + } + } + // If Ref is set on the SchemaRef, it means that this type is actually a reference to // another type. We're not de-referencing, so simply use the referenced type. if IsGoTypeReference(sref.Ref) { @@ -236,15 +300,18 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { sref.Ref, err) } return Schema{ - GoType: refType, - Description: schema.Description, - DefineViaAlias: true, + GoType: refType, + Description: schema.Description, + DefineViaAlias: true, + OAPISchema: schema, + SkipOptionalPointer: skipOptionalPointer, }, nil } outSchema := Schema{ - Description: schema.Description, - OAPISchema: schema, + Description: schema.Description, + OAPISchema: schema, + SkipOptionalPointer: skipOptionalPointer, } // AllOf is interesting, and useful. It's the union of a number of other @@ -260,7 +327,8 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { return mergedSchema, nil } - // Check for custom Go type extension + // Check x-go-type, which will completely override the definition of this + // schema with the provided type. if extension, ok := schema.Extensions[extPropGoType]; ok { typeName, err := extTypeName(extension) if err != nil { @@ -268,26 +336,30 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { } outSchema.GoType = typeName outSchema.DefineViaAlias = true + return outSchema, nil } // Schema type and format, eg. string / binary t := schema.Type // Handle objects and empty schemas first as a special case - if t == "" || t == "object" { + if t.Slice() == nil || t.Is("object") { var outType string if len(schema.Properties) == 0 && !SchemaHasAdditionalProperties(schema) && schema.AnyOf == nil && schema.OneOf == nil { // If the object has no properties or additional properties, we // have some special cases for its type. - if t == "object" { + if t.Is("object") { // We have an object with no properties. This is a generic object // expressed as a map. outType = "map[string]interface{}" + setSkipOptionalPointerForContainerType(&outSchema) } else { // t == "" // If we don't even have the object designator, we're a completely // generic type. outType = "interface{}" + // this should never have an "optional pointer", as it doesn't make sense to be a `*interface{}` + outSchema.SkipOptionalPointer = true } outSchema.GoType = outType outSchema.DefineViaAlias = true @@ -344,6 +416,7 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { // since we don't need them for a simple map. outSchema.HasAdditionalProperties = false outSchema.GoType = fmt.Sprintf("map[string]%s", additionalPropertiesType(outSchema)) + setSkipOptionalPointerForContainerType(&outSchema) return outSchema, nil } @@ -387,8 +460,12 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { ReadOnly: p.Value.ReadOnly, WriteOnly: p.Value.WriteOnly, Extensions: p.Value.Extensions, + Deprecated: p.Value.Deprecated, } outSchema.Properties = append(outSchema.Properties, prop) + if len(pSchema.AdditionalTypes) > 0 { + outSchema.AdditionalTypes = append(outSchema.AdditionalTypes, pSchema.AdditionalTypes...) + } } if schema.AnyOf != nil { @@ -404,6 +481,28 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { outSchema.GoType = GenStructFromSchema(outSchema) } + + // Check for x-go-type-name. It behaves much like x-go-type, however, it will + // create a type definition for the named type, and use the named type in place + // of this schema. + if extension, ok := schema.Extensions[extGoTypeName]; ok { + typeName, err := extTypeName(extension) + if err != nil { + return outSchema, fmt.Errorf("invalid value for %q: %w", extGoTypeName, err) + } + + newTypeDef := TypeDefinition{ + TypeName: typeName, + Schema: outSchema, + } + outSchema = Schema{ + Description: newTypeDef.Schema.Description, + GoType: typeName, + DefineViaAlias: true, + AdditionalTypes: append(outSchema.AdditionalTypes, newTypeDef), + } + } + return outSchema, nil } else if len(schema.Enum) > 0 { err := oapiSchemaToGoType(schema, path, &outSchema) @@ -422,8 +521,8 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { enumNames := enumValues for _, key := range []string{extEnumVarNames, extEnumNames} { - if _, ok := schema.Extensions[key]; ok { - if extEnumNames, err := extParseEnumVarNames(schema.Extensions[key]); err == nil { + if extension, ok := schema.Extensions[key]; ok { + if extEnumNames, err := extParseEnumVarNames(extension); err == nil { enumNames = extEnumNames break } @@ -448,7 +547,7 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { } if len(path) > 1 { // handle additional type only on non-toplevel types // Allow overriding autogenerated enum type names, since these may - // cause conflicts - see https://github.com/deepmap/oapi-codegen/issues/832 + // cause conflicts - see https://github.com/oapi-codegen/oapi-codegen/issues/832 var typeName string if extension, ok := schema.Extensions[extGoTypeName]; ok { typeName, err = extString(extension) @@ -482,14 +581,14 @@ func oapiSchemaToGoType(schema *openapi3.Schema, path []string, outSchema *Schem f := schema.Format t := schema.Type - switch t { - case "array": + if t.Is("array") { // For arrays, we'll get the type of the Items and throw a // [] in front of it. arrayType, err := GenerateGoSchema(schema.Items, path) if err != nil { return fmt.Errorf("error generating type for array: %w", err) } + if (arrayType.HasAdditionalProperties || len(arrayType.UnionElements) != 0) && arrayType.RefType == "" { // If we have items which have additional properties or union values, // but are not a pre-defined type, we need to define a type @@ -506,78 +605,51 @@ func oapiSchemaToGoType(schema *openapi3.Schema, path []string, outSchema *Schem arrayType.RefType = typeName } + + typeDeclaration := arrayType.TypeDecl() + if arrayType.OAPISchema != nil && arrayType.OAPISchema.Nullable { + if globalState.options.OutputOptions.NullableType { + typeDeclaration = "nullable.Nullable[" + typeDeclaration + "]" + } else { + typeDeclaration = "*" + typeDeclaration + } + } + outSchema.ArrayType = &arrayType - outSchema.GoType = "[]" + arrayType.TypeDecl() + outSchema.GoType = "[]" + typeDeclaration outSchema.AdditionalTypes = arrayType.AdditionalTypes outSchema.Properties = arrayType.Properties outSchema.DefineViaAlias = true - case "integer": - // We default to int if format doesn't ask for something else. - if f == "int64" { - outSchema.GoType = "int64" - } else if f == "int32" { - outSchema.GoType = "int32" - } else if f == "int16" { - outSchema.GoType = "int16" - } else if f == "int8" { - outSchema.GoType = "int8" - } else if f == "int" { - outSchema.GoType = "int" - } else if f == "uint64" { - outSchema.GoType = "uint64" - } else if f == "uint32" { - outSchema.GoType = "uint32" - } else if f == "uint16" { - outSchema.GoType = "uint16" - } else if f == "uint8" { - outSchema.GoType = "uint8" - } else if f == "uint" { - outSchema.GoType = "uint" - } else { - outSchema.GoType = "int" + if sliceContains(globalState.options.OutputOptions.DisableTypeAliasesForType, "array") { + outSchema.DefineViaAlias = false } + setSkipOptionalPointerForContainerType(outSchema) + + } else if t.Is("integer") { + spec := globalState.typeMapping.Integer.Resolve(f) + outSchema.GoType = spec.Type outSchema.DefineViaAlias = true - case "number": - // We default to float for "number" - if f == "double" { - outSchema.GoType = "float64" - } else if f == "float" || f == "" { - outSchema.GoType = "float32" - } else { - return fmt.Errorf("invalid number format: %s", f) - } + } else if t.Is("number") { + spec := globalState.typeMapping.Number.Resolve(f) + outSchema.GoType = spec.Type outSchema.DefineViaAlias = true - case "boolean": - if f != "" { - return fmt.Errorf("invalid format (%s) for boolean", f) - } - outSchema.GoType = "bool" + } else if t.Is("boolean") { + spec := globalState.typeMapping.Boolean.Resolve(f) + outSchema.GoType = spec.Type outSchema.DefineViaAlias = true - case "string": - // Special case string formats here. - switch f { - case "byte": - outSchema.GoType = "[]byte" - case "email": - outSchema.GoType = "openapi_types.Email" - case "date": - outSchema.GoType = "openapi_types.Date" - case "date-time": - outSchema.GoType = "time.Time" - case "json": - outSchema.GoType = "json.RawMessage" + } else if t.Is("string") { + spec := globalState.typeMapping.String.Resolve(f) + outSchema.GoType = spec.Type + // Preserve special behaviors for specific types + if outSchema.GoType == "[]byte" { + setSkipOptionalPointerForContainerType(outSchema) + } + if outSchema.GoType == "json.RawMessage" { outSchema.SkipOptionalPointer = true - case "uuid": - outSchema.GoType = "openapi_types.UUID" - case "binary": - outSchema.GoType = "openapi_types.File" - default: - // All unrecognized formats are simply a regular string. - outSchema.GoType = "string" } outSchema.DefineViaAlias = true - default: - return fmt.Errorf("unhandled Schema type: %s", t) + } else { + return fmt.Errorf("unhandled Schema type: %v", t) } return nil } @@ -597,6 +669,13 @@ type FieldDescriptor struct { IsRef bool // Is this schema a reference to predefined object? } +func stringOrEmpty(b bool, s string) string { + if b { + return s + } + return "" +} + // GenFieldsFromProperties produce corresponding field names with JSON annotations, // given a list of schema descriptors func GenFieldsFromProperties(props []Property) []string { @@ -605,11 +684,6 @@ func GenFieldsFromProperties(props []Property) []string { field := "" goFieldName := p.GoFieldName() - if _, ok := p.Extensions[extGoName]; ok { - if extGoFieldName, err := extParseGoFieldName(p.Extensions[extGoName]); err == nil { - goFieldName = extGoFieldName - } - } // Add a comment to a field in case we have one, otherwise skip. if p.Description != "" { @@ -621,33 +695,69 @@ func GenFieldsFromProperties(props []Property) []string { field += fmt.Sprintf("%s\n", StringWithTypeNameToGoComment(p.Description, p.GoFieldName())) } - field += fmt.Sprintf(" %s %s", goFieldName, p.GoTypeDef()) + if p.Deprecated { + // This comment has to be on its own line for godoc & IDEs to pick up + var deprecationReason string + if extension, ok := p.Extensions[extDeprecationReason]; ok { + if extDeprecationReason, err := extParseDeprecationReason(extension); err == nil { + deprecationReason = extDeprecationReason + } + } + + field += fmt.Sprintf("%s\n", DeprecationComment(deprecationReason)) + } - // Support x-omitempty - overrideOmitEmpty := true - if _, ok := p.Extensions[extPropOmitEmpty]; ok { - if extOmitEmpty, err := extParseOmitEmpty(p.Extensions[extPropOmitEmpty]); err == nil { - overrideOmitEmpty = extOmitEmpty + // Check x-go-type-skip-optional-pointer, which will override if the type + // should be a pointer or not when the field is optional. + if extension, ok := p.Extensions[extPropGoTypeSkipOptionalPointer]; ok { + if skipOptionalPointer, err := extParsePropGoTypeSkipOptionalPointer(extension); err == nil { + p.Schema.SkipOptionalPointer = skipOptionalPointer } } - fieldTags := make(map[string]string) + field += fmt.Sprintf(" %s %s", goFieldName, p.GoTypeDef()) + + shouldOmitEmpty := (!p.Required || p.ReadOnly || p.WriteOnly) && + (!p.Required || !p.ReadOnly || !globalState.options.Compatibility.DisableRequiredReadOnlyAsPointer) + + omitEmpty := shouldOmitEmpty + + omitZero := false + + // default, but allow turning of + if shouldOmitEmpty && p.Schema.SkipOptionalPointer && globalState.options.OutputOptions.PreferSkipOptionalPointerWithOmitzero { + omitZero = true + } - if (p.Required && !p.ReadOnly && !p.WriteOnly) || p.Nullable || !overrideOmitEmpty || (p.Required && p.ReadOnly && globalState.options.Compatibility.DisableRequiredReadOnlyAsPointer) { - fieldTags["json"] = p.JsonFieldName - if p.NeedsFormTag { - fieldTags["form"] = p.JsonFieldName + // Support x-omitempty and x-omitzero + if extOmitEmptyValue, ok := p.Extensions[extPropOmitEmpty]; ok { + if xValue, err := extParseOmitEmpty(extOmitEmptyValue); err == nil { + omitEmpty = xValue } - } else { - fieldTags["json"] = p.JsonFieldName + ",omitempty" - if p.NeedsFormTag { - fieldTags["form"] = p.JsonFieldName + ",omitempty" + } + + if extOmitEmptyValue, ok := p.Extensions[extPropOmitZero]; ok { + if xValue, err := extParseOmitZero(extOmitEmptyValue); err == nil { + omitZero = xValue } } + fieldTags := make(map[string]string) + + fieldTags["json"] = p.JsonFieldName + + stringOrEmpty(omitEmpty, ",omitempty") + + stringOrEmpty(omitZero, ",omitzero") + + if globalState.options.OutputOptions.EnableYamlTags { + fieldTags["yaml"] = p.JsonFieldName + stringOrEmpty(omitEmpty, ",omitempty") + } + if p.NeedsFormTag { + fieldTags["form"] = p.JsonFieldName + stringOrEmpty(omitEmpty, ",omitempty") + } + // Support x-go-json-ignore - if _, ok := p.Extensions[extPropGoJsonIgnore]; ok { - if goJsonIgnore, err := extParseGoJsonIgnore(p.Extensions[extPropGoJsonIgnore]); err == nil && goJsonIgnore { + if extension, ok := p.Extensions[extPropGoJsonIgnore]; ok { + if goJsonIgnore, err := extParseGoJsonIgnore(extension); err == nil && goJsonIgnore { fieldTags["json"] = "-" } } @@ -655,14 +765,14 @@ func GenFieldsFromProperties(props []Property) []string { // Support x-oapi-codegen-extra-tags if extension, ok := p.Extensions[extPropExtraTags]; ok { if tags, err := extExtraTags(extension); err == nil { - keys := SortedStringKeys(tags) + keys := SortedMapKeys(tags) for _, k := range keys { fieldTags[k] = tags[k] } } } // Convert the fieldTags map into Go field annotations. - keys := SortedStringKeys(fieldTags) + keys := SortedMapKeys(fieldTags) tags := make([]string, len(keys)) for i, k := range keys { tags[i] = fmt.Sprintf(`%s:"%s"`, k, fieldTags[k]) @@ -678,6 +788,9 @@ func additionalPropertiesType(schema Schema) string { if schema.AdditionalPropertiesType.RefType != "" { addPropsType = schema.AdditionalPropertiesType.RefType } + if schema.AdditionalPropertiesType.OAPISchema != nil && schema.AdditionalPropertiesType.OAPISchema.Nullable { + addPropsType = "*" + addPropsType + } return addPropsType } @@ -745,29 +858,61 @@ func generateUnion(outSchema *Schema, elements openapi3.SchemaRefs, discriminato refToGoTypeMap := make(map[string]string) for i, element := range elements { - elementSchema, err := GenerateGoSchema(element, path) + elementPath := append(path, fmt.Sprint(i)) + elementSchema, err := GenerateGoSchema(element, elementPath) if err != nil { return err } if element.Ref == "" { - td := TypeDefinition{Schema: elementSchema, TypeName: SchemaNameToTypeName(PathToTypeName(append(path, fmt.Sprint(i))))} - outSchema.AdditionalTypes = append(outSchema.AdditionalTypes, td) - elementSchema.GoType = td.TypeName + elementName := SchemaNameToTypeName(PathToTypeName(elementPath)) + if elementSchema.TypeDecl() == elementName { + elementSchema.GoType = elementName + } else { + td := TypeDefinition{Schema: elementSchema, TypeName: elementName} + outSchema.AdditionalTypes = append(outSchema.AdditionalTypes, td) + elementSchema.GoType = td.TypeName + } + outSchema.AdditionalTypes = append(outSchema.AdditionalTypes, elementSchema.AdditionalTypes...) } else { refToGoTypeMap[element.Ref] = elementSchema.GoType } if discriminator != nil { + if len(discriminator.Mapping) != 0 && element.Ref == "" { + return errors.New("ambiguous discriminator.mapping: please replace inlined object with $ref") + } + + // Explicit mapping. + var mapped bool for k, v := range discriminator.Mapping { if v == element.Ref { outSchema.Discriminator.Mapping[k] = elementSchema.GoType - break + mapped = true } } + // Implicit mapping. + if !mapped { + outSchema.Discriminator.Mapping[RefPathToObjName(element.Ref)] = elementSchema.GoType + } } outSchema.UnionElements = append(outSchema.UnionElements, UnionElement(elementSchema.GoType)) } + if (outSchema.Discriminator != nil) && len(outSchema.Discriminator.Mapping) < len(elements) { + return errors.New("discriminator: not all schemas were mapped") + } + return nil } + +// setSkipOptionalPointerForContainerType ensures that the "optional pointer" is skipped on container types (such as a slice or a map). +// This is controlled using the `prefer-skip-optional-pointer-on-container-types` Output Option +// NOTE that it is still possible to override this on a per-field basis with `x-go-type-skip-optional-pointer` +func setSkipOptionalPointerForContainerType(outSchema *Schema) { + if !globalState.options.OutputOptions.PreferSkipOptionalPointerOnContainerTypes { + return + } + + outSchema.SkipOptionalPointer = true +} diff --git a/pkg/codegen/schema_test.go b/pkg/codegen/schema_test.go new file mode 100644 index 0000000000..6075594a32 --- /dev/null +++ b/pkg/codegen/schema_test.go @@ -0,0 +1,521 @@ +package codegen + +import ( + "testing" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestProperty_GoTypeDef(t *testing.T) { + type fields struct { + GlobalStateDisableRequiredReadOnlyAsPointer bool + Schema Schema + Required bool + Nullable bool + ReadOnly bool + WriteOnly bool + } + tests := []struct { + name string + fields fields + want string + }{ + { + // When pointer is skipped by setting flag SkipOptionalPointer, the + // flag will never be pointer irrespective of other flags. + name: "Set skip optional pointer type for go type", + fields: fields{ + Schema: Schema{ + SkipOptionalPointer: true, + RefType: "", + GoType: "int", + }, + }, + want: "int", + }, + + { + // if the field is optional, it will always be pointer irrespective of other + // flags, given that pointer type is not skipped by setting SkipOptionalPointer + // flag to true + name: "When the field is optional", + fields: fields{ + Schema: Schema{ + SkipOptionalPointer: false, + RefType: "", + GoType: "int", + }, + Required: false, + }, + want: "*int", + }, + + { + // if the field(custom-type) is optional, it will NOT be a pointer if + // SkipOptionalPointer flag is set to true + name: "Set skip optional pointer type for ref type", + fields: fields{ + Schema: Schema{ + SkipOptionalPointer: true, + RefType: "CustomType", + GoType: "int", + }, + Required: false, + }, + want: "CustomType", + }, + + // For the following test cases, SkipOptionalPointer flag is false. + { + name: "When field is required and not nullable", + fields: fields{ + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + Required: true, + Nullable: false, + }, + want: "int", + }, + + { + name: "When field is required and nullable", + fields: fields{ + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + Required: true, + Nullable: true, + }, + want: "*int", + }, + + { + name: "When field is optional and not nullable", + fields: fields{ + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + Required: false, + Nullable: false, + }, + want: "*int", + }, + + { + name: "When field is optional and nullable", + fields: fields{ + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + Required: false, + Nullable: true, + }, + want: "*int", + }, + + // Following tests cases for non-nullable and required; and skip pointer is not opted + { + name: "When field is readOnly it will always be pointer", + fields: fields{ + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + ReadOnly: true, + Required: true, + }, + want: "*int", + }, + + { + name: "When field is readOnly and read only pointer disabled", + fields: fields{ + GlobalStateDisableRequiredReadOnlyAsPointer: true, + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + ReadOnly: true, + Required: true, + }, + want: "int", + }, + + { + name: "When field is readOnly and optional", + fields: fields{ + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + ReadOnly: true, + Required: false, + }, + want: "*int", + }, + { + name: "When field is readOnly and optional and read only pointer disabled", + fields: fields{ + GlobalStateDisableRequiredReadOnlyAsPointer: true, + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + ReadOnly: true, + Required: false, + }, + want: "*int", + }, + + // When field is write only, it will always be pointer unless pointer is + // skipped by setting SkipOptionalPointer flag + { + name: "When field is write only and read only pointer disabled", + fields: fields{ + GlobalStateDisableRequiredReadOnlyAsPointer: true, + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + WriteOnly: true, + }, + want: "*int", + }, + + { + name: "When field is write only and read only pointer enabled", + fields: fields{ + GlobalStateDisableRequiredReadOnlyAsPointer: false, + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + WriteOnly: true, + }, + want: "*int", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + globalState.options.Compatibility.DisableRequiredReadOnlyAsPointer = tt.fields.GlobalStateDisableRequiredReadOnlyAsPointer + p := Property{ + Schema: tt.fields.Schema, + Required: tt.fields.Required, + Nullable: tt.fields.Nullable, + ReadOnly: tt.fields.ReadOnly, + WriteOnly: tt.fields.WriteOnly, + } + assert.Equal(t, tt.want, p.GoTypeDef()) + }) + } +} + +func TestProperty_GoTypeDef_nullable(t *testing.T) { + type fields struct { + GlobalStateDisableRequiredReadOnlyAsPointer bool + GlobalStateNullableType bool + Schema Schema + Required bool + Nullable bool + ReadOnly bool + WriteOnly bool + } + tests := []struct { + name string + fields fields + want string + }{ + { + // Field not nullable. + // When pointer is skipped by setting flag SkipOptionalPointer, the + // flag will never be pointer irrespective of other flags. + name: "Set skip optional pointer type for go type", + fields: fields{ + GlobalStateNullableType: true, + Schema: Schema{ + SkipOptionalPointer: true, + RefType: "", + GoType: "int", + }, + }, + want: "int", + }, + + { + // Field not nullable. + // if the field is optional, it will always be pointer irrespective of other + // flags, given that pointer type is not skipped by setting SkipOptionalPointer + // flag to true + name: "When the field is optional", + fields: fields{ + GlobalStateNullableType: true, + Schema: Schema{ + SkipOptionalPointer: false, + RefType: "", + GoType: "int", + }, + Required: false, + }, + want: "*int", + }, + + { + // Field not nullable. + // if the field(custom type) is optional, it will NOT be a pointer if + // SkipOptionalPointer flag is set to true + name: "Set skip optional pointer type for ref type", + fields: fields{ + GlobalStateNullableType: true, + Schema: Schema{ + SkipOptionalPointer: true, + RefType: "CustomType", + GoType: "int", + }, + Required: false, + }, + want: "CustomType", + }, + + // Field not nullable. + // For the following test case, SkipOptionalPointer flag is false. + { + name: "When field is required and not nullable", + fields: fields{ + GlobalStateNullableType: true, + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + Required: true, + Nullable: false, + }, + want: "int", + }, + + { + name: "When field is required and nullable", + fields: fields{ + GlobalStateNullableType: true, + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + Required: true, + Nullable: true, + }, + want: "nullable.Nullable[int]", + }, + + { + name: "When field is optional and not nullable", + fields: fields{ + GlobalStateNullableType: true, + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + Required: false, + Nullable: false, + }, + want: "*int", + }, + + { + name: "When field is optional and nullable", + fields: fields{ + GlobalStateNullableType: true, + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + Required: false, + Nullable: true, + }, + want: "nullable.Nullable[int]", + }, + + { + name: "When field is readOnly, non-nullable and required and skip pointer is not opted", + fields: fields{ + GlobalStateNullableType: true, + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + ReadOnly: true, + Required: true, + }, + want: "*int", + }, + + { + name: "When field is readOnly, required, non-nullable and read only pointer disabled", + fields: fields{ + GlobalStateNullableType: true, + GlobalStateDisableRequiredReadOnlyAsPointer: true, + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + ReadOnly: true, + Required: true, + }, + want: "int", + }, + + { + name: "When field is readOnly, optional and non nullable", + fields: fields{ + GlobalStateNullableType: true, + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + ReadOnly: true, + Required: false, + }, + want: "*int", + }, + { + name: "When field is readOnly and optional and read only pointer disabled", + fields: fields{ + GlobalStateNullableType: true, + GlobalStateDisableRequiredReadOnlyAsPointer: true, + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + ReadOnly: true, + Required: false, + }, + want: "*int", + }, + + { + name: "When field is write only and non nullable", + fields: fields{ + GlobalStateNullableType: true, + GlobalStateDisableRequiredReadOnlyAsPointer: true, + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + WriteOnly: true, + }, + want: "*int", + }, + + { + name: "When field is write only and nullable", + fields: fields{ + GlobalStateNullableType: true, + GlobalStateDisableRequiredReadOnlyAsPointer: true, + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + WriteOnly: true, + Nullable: true, + }, + want: "nullable.Nullable[int]", + }, + + { + name: "When field is write only, nullable and read only pointer enabled", + fields: fields{ + GlobalStateNullableType: true, + Schema: Schema{ + SkipOptionalPointer: false, + GoType: "int", + }, + WriteOnly: true, + Nullable: true, + }, + want: "nullable.Nullable[int]", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + globalState.options.Compatibility.DisableRequiredReadOnlyAsPointer = tt.fields.GlobalStateDisableRequiredReadOnlyAsPointer + globalState.options.OutputOptions.NullableType = tt.fields.GlobalStateNullableType + p := Property{ + Schema: tt.fields.Schema, + Required: tt.fields.Required, + Nullable: tt.fields.Nullable, + ReadOnly: tt.fields.ReadOnly, + WriteOnly: tt.fields.WriteOnly, + } + assert.Equal(t, tt.want, p.GoTypeDef()) + }) + } +} + +func TestProperty_ZeroValueIsNil(t *testing.T) { + newType := func(typ string) *openapi3.Types { + return &openapi3.Types{typ} + } + + tests := []struct { + name string + oapiSchema *openapi3.Schema + expectIsNil bool + }{ + { + name: "when an array, returns true", + oapiSchema: &openapi3.Schema{Type: newType("array")}, + expectIsNil: true, + }, + { + name: "when an object, returns false", + oapiSchema: &openapi3.Schema{Type: newType("object")}, + expectIsNil: false, + }, + { + name: "when a string, returns false", + oapiSchema: &openapi3.Schema{Type: newType("string")}, + expectIsNil: false, + }, + { + name: "when an integer, returns false", + oapiSchema: &openapi3.Schema{Type: newType("integer")}, + expectIsNil: false, + }, + { + name: "when a number, returns false", + oapiSchema: &openapi3.Schema{Type: newType("number")}, + expectIsNil: false, + }, + { + name: "when OAPISchema is nil, returns false", + oapiSchema: nil, + expectIsNil: false, + }, + { + name: "when OAPISchema is zero value, returns false", + oapiSchema: &openapi3.Schema{}, + expectIsNil: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + prop := Property{ + Schema: Schema{ + OAPISchema: tt.oapiSchema, + }, + } + if tt.expectIsNil { + require.True(t, prop.ZeroValueIsNil()) + } else { + require.False(t, prop.ZeroValueIsNil()) + } + }) + } +} diff --git a/pkg/codegen/server_urls.go b/pkg/codegen/server_urls.go new file mode 100644 index 0000000000..d10c2d9f60 --- /dev/null +++ b/pkg/codegen/server_urls.go @@ -0,0 +1,81 @@ +package codegen + +import ( + "fmt" + "strconv" + "text/template" + + "github.com/getkin/kin-openapi/openapi3" +) + +const serverURLPrefix = "ServerUrl" +const serverURLSuffixIterations = 10 + +// ServerObjectDefinition defines the definition of an OpenAPI Server object (https://spec.openapis.org/oas/v3.0.3#server-object) as it is provided to code generation in `oapi-codegen` +type ServerObjectDefinition struct { + // GoName is the name of the variable for this Server URL + GoName string + + // OAPISchema is the underlying OpenAPI representation of the Server + OAPISchema *openapi3.Server +} + +func GenerateServerURLs(t *template.Template, spec *openapi3.T) (string, error) { + names := make(map[string]*openapi3.Server) + + for _, server := range spec.Servers { + suffix := server.Description + if suffix == "" { + suffix = nameNormalizer(server.URL) + } + name := serverURLPrefix + UppercaseFirstCharacter(suffix) + name = nameNormalizer(name) + + // if this is the only type with this name, store it + if _, conflict := names[name]; !conflict { + names[name] = server + continue + } + + // otherwise, try appending a number to the name + saved := false + // NOTE that we start at 1 on purpose, as + // + // ... ServerURLDevelopmentServer + // ... ServerURLDevelopmentServer1` + // + // reads better than: + // + // ... ServerURLDevelopmentServer + // ... ServerURLDevelopmentServer0 + for i := 1; i < 1+serverURLSuffixIterations; i++ { + suffixed := name + strconv.Itoa(i) + // and then store it if there's no conflict + if _, suffixConflict := names[suffixed]; !suffixConflict { + names[suffixed] = server + saved = true + break + } + } + + if saved { + continue + } + + // otherwise, error + return "", fmt.Errorf("failed to create a unique name for the Server URL (%#v) with description (%#v) after %d iterations", server.URL, server.Description, serverURLSuffixIterations) + } + + keys := SortedMapKeys(names) + servers := make([]ServerObjectDefinition, len(keys)) + i := 0 + for _, k := range keys { + servers[i] = ServerObjectDefinition{ + GoName: k, + OAPISchema: names[k], + } + i++ + } + + return GenerateTemplates([]string{"server-urls.tmpl"}, t, servers) +} diff --git a/pkg/codegen/template_helpers.go b/pkg/codegen/template_helpers.go index 7585a26801..e4d932116a 100644 --- a/pkg/codegen/template_helpers.go +++ b/pkg/codegen/template_helpers.go @@ -20,10 +20,11 @@ import ( "strings" "text/template" - "github.com/deepmap/oapi-codegen/pkg/util" - "github.com/labstack/echo/v4" "golang.org/x/text/cases" "golang.org/x/text/language" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/oapi-codegen/oapi-codegen/v2/pkg/util" ) const ( @@ -34,16 +35,17 @@ const ( ) var ( - contentTypesJSON = []string{echo.MIMEApplicationJSON, "text/x-json", "application/problem+json"} - contentTypesYAML = []string{"application/yaml", "application/x-yaml", "text/yaml", "text/x-yaml"} - contentTypesXML = []string{echo.MIMEApplicationXML, echo.MIMETextXML, "application/problems+xml"} + contentTypesJSON = []string{"application/json", "text/x-json", "application/problem+json"} + contentTypesHalJSON = []string{"application/hal+json"} + contentTypesYAML = []string{"application/yaml", "application/x-yaml", "text/yaml", "text/x-yaml"} + contentTypesXML = []string{"application/xml", "text/xml", "application/problems+xml"} responseTypeSuffix = "Response" titleCaser = cases.Title(language.English) ) -// This function takes an array of Parameter definition, and generates a valid +// genParamArgs takes an array of Parameter definition, and generates a valid // Go parameter declaration from them, eg: // ", foo int, bar string, baz float32". The preceding comma is there to save // a lot of work in the template engine. @@ -59,7 +61,7 @@ func genParamArgs(params []ParameterDefinition) string { return ", " + strings.Join(parts, ", ") } -// This function is much like the one above, except it only produces the +// genParamTypes is much like the one above, except it only produces the // types of the parameters for a type declaration. It would produce this // from the same input as above: // ", int, string, float32". @@ -101,7 +103,7 @@ func genResponsePayload(operationID string) string { return buffer.String() } -// genResponseUnmarshal generates unmarshalling steps for structured response payloads +// genResponseUnmarshal generates unmarshaling steps for structured response payloads func genResponseUnmarshal(op *OperationDefinition) string { var handledCaseClauses = make(map[string]string) var unhandledCaseClauses = make(map[string]string) @@ -122,8 +124,8 @@ func genResponseUnmarshal(op *OperationDefinition) string { responses := op.Spec.Responses for _, typeDefinition := range typeDefinitions { - responseRef, ok := responses[typeDefinition.ResponseName] - if !ok { + responseRef := responses.Value(typeDefinition.ResponseName) + if responseRef == nil { continue } @@ -133,7 +135,7 @@ func genResponseUnmarshal(op *OperationDefinition) string { continue } - // If there is no content-type then we have no unmarshalling to do: + // If there is no content-type then we have no unmarshaling to do: if len(responseRef.Value.Content) == 0 { caseAction := "break // No content-type" caseClauseKey := "case " + getConditionOfResponseName("rsp.StatusCode", typeDefinition.ResponseName) + ":" @@ -141,9 +143,16 @@ func genResponseUnmarshal(op *OperationDefinition) string { continue } - // If we made it this far then we need to handle unmarshalling for each content-type: - sortedContentKeys := SortedContentKeys(responseRef.Value.Content) - for _, contentTypeName := range sortedContentKeys { + // If we made it this far then we need to handle unmarshaling for each content-type: + SortedMapKeys := SortedMapKeys(responseRef.Value.Content) + jsonCount := 0 + for _, contentTypeName := range SortedMapKeys { + if StringInArray(contentTypeName, contentTypesJSON) || util.IsMediaTypeJson(contentTypeName) { + jsonCount++ + } + } + + for _, contentTypeName := range SortedMapKeys { // We get "interface{}" when using "anyOf" or "oneOf" (which doesn't work with Go types): if typeDefinition.TypeName == "interface{}" { @@ -165,8 +174,13 @@ func genResponseUnmarshal(op *OperationDefinition) string { typeDefinition.Schema.TypeDecl(), typeDefinition.TypeName) - caseKey, caseClause := buildUnmarshalCase(typeDefinition, caseAction, "json") - handledCaseClauses[caseKey] = caseClause + if jsonCount > 1 { + caseKey, caseClause := buildUnmarshalCaseStrict(typeDefinition, caseAction, contentTypeName) + handledCaseClauses[caseKey] = caseClause + } else { + caseKey, caseClause := buildUnmarshalCase(typeDefinition, caseAction, "json") + handledCaseClauses[caseKey] = caseClause + } } // YAML: @@ -212,14 +226,14 @@ func genResponseUnmarshal(op *OperationDefinition) string { } // Now build the switch statement in order of most-to-least specific: - // See: https://github.com/deepmap/oapi-codegen/issues/127 for why we handle this in two separate + // See: https://github.com/oapi-codegen/oapi-codegen/issues/127 for why we handle this in two separate // groups. fmt.Fprintf(buffer, "switch {\n") - for _, caseClauseKey := range SortedStringKeys(handledCaseClauses) { + for _, caseClauseKey := range SortedMapKeys(handledCaseClauses) { fmt.Fprintf(buffer, "%s\n", handledCaseClauses[caseClauseKey]) } - for _, caseClauseKey := range SortedStringKeys(unhandledCaseClauses) { + for _, caseClauseKey := range SortedMapKeys(unhandledCaseClauses) { fmt.Fprintf(buffer, "%s\n", unhandledCaseClauses[caseClauseKey]) } @@ -228,16 +242,30 @@ func genResponseUnmarshal(op *OperationDefinition) string { return buffer.String() } -// buildUnmarshalCase builds an unmarshalling case clause for different content-types: +// buildUnmarshalCase builds an unmarshaling case clause for different content-types: func buildUnmarshalCase(typeDefinition ResponseTypeDefinition, caseAction string, contentType string) (caseKey string, caseClause string) { caseKey = fmt.Sprintf("%s.%s.%s", prefixLeastSpecific, contentType, typeDefinition.ResponseName) caseClauseKey := getConditionOfResponseName("rsp.StatusCode", typeDefinition.ResponseName) - caseClause = fmt.Sprintf("case strings.Contains(rsp.Header.Get(\"%s\"), \"%s\") && %s:\n%s\n", echo.HeaderContentType, contentType, caseClauseKey, caseAction) + contentTypeLiteral := StringToGoString(contentType) + caseClause = fmt.Sprintf("case strings.Contains(rsp.Header.Get(\"%s\"), %s) && %s:\n%s\n", "Content-Type", contentTypeLiteral, caseClauseKey, caseAction) return caseKey, caseClause } -// genResponseTypeName creates the name of generated response types (given the operationID): +func buildUnmarshalCaseStrict(typeDefinition ResponseTypeDefinition, caseAction string, contentType string) (caseKey string, caseClause string) { + caseKey = fmt.Sprintf("%s.%s.%s", prefixLeastSpecific, contentType, typeDefinition.ResponseName) + caseClauseKey := getConditionOfResponseName("rsp.StatusCode", typeDefinition.ResponseName) + contentTypeLiteral := StringToGoString(contentType) + caseClause = fmt.Sprintf("case rsp.Header.Get(\"%s\") == %s && %s:\n%s\n", "Content-Type", contentTypeLiteral, caseClauseKey, caseAction) + return caseKey, caseClause +} + +// genResponseTypeName creates the name of generated response types (given the operationID). +// It first checks if the multi-pass name resolver has assigned a name for this +// wrapper type (which would happen if the default name collides with a schema type). func genResponseTypeName(operationID string) string { + if name, ok := globalState.resolvedClientWrapperNames[operationID]; ok { + return name + } return fmt.Sprintf("%s%s", UppercaseFirstCharacter(operationID), responseTypeSuffix) } @@ -263,7 +291,11 @@ func getConditionOfResponseName(statusCodeVar, responseName string) string { // This outputs a string array func toStringArray(sarr []string) string { - return `[]string{"` + strings.Join(sarr, `","`) + `"}` + s := strings.Join(sarr, `","`) + if len(s) > 0 { + s = `"` + s + `"` + } + return `[]string{` + s + `}` } func stripNewLines(s string) string { @@ -271,6 +303,26 @@ func stripNewLines(s string) string { return r.Replace(s) } +// genServerURLWithVariablesFunctionParams is a template helper method to generate the function parameters for the generated function for a Server object that contains `variables` (https://spec.openapis.org/oas/v3.0.3#server-object) +// +// goTypePrefix is the prefix being used to create underlying types in the template (likely the `ServerObjectDefinition.GoName`) +// variables are this `ServerObjectDefinition`'s variables for the Server object (likely the `ServerObjectDefinition.OAPISchema`) +func genServerURLWithVariablesFunctionParams(goTypePrefix string, variables map[string]*openapi3.ServerVariable) string { + keys := SortedMapKeys(variables) + + if len(variables) == 0 { + return "" + } + parts := make([]string, len(variables)) + + for i := range keys { + k := keys[i] + variableDefinitionPrefix := goTypePrefix + UppercaseFirstCharacter(k) + "Variable" + parts[i] = k + " " + variableDefinitionPrefix + } + return strings.Join(parts, ", ") +} + // TemplateFunctions is passed to the template engine, and we can call each // function here by keyName from the template code. var TemplateFunctions = template.FuncMap{ @@ -278,10 +330,13 @@ var TemplateFunctions = template.FuncMap{ "genParamTypes": genParamTypes, "genParamNames": genParamNames, "genParamFmtString": ReplacePathParamsWithStr, + "swaggerUriToIrisUri": SwaggerUriToIrisUri, "swaggerUriToEchoUri": SwaggerUriToEchoUri, + "swaggerUriToFiberUri": SwaggerUriToFiberUri, "swaggerUriToChiUri": SwaggerUriToChiUri, "swaggerUriToGinUri": SwaggerUriToGinUri, "swaggerUriToGorillaUri": SwaggerUriToGorillaUri, + "swaggerUriToStdHttpUri": SwaggerUriToStdHttpUri, "lcFirst": LowercaseFirstCharacter, "ucFirst": UppercaseFirstCharacter, "ucFirstWithPkgName": UppercaseFirstCharacterWithPkgName, @@ -295,5 +350,8 @@ var TemplateFunctions = template.FuncMap{ "title": titleCaser.String, "stripNewLines": stripNewLines, "sanitizeGoIdentity": SanitizeGoIdentity, + "toGoString": StringToGoString, "toGoComment": StringWithTypeNameToGoComment, + + "genServerURLWithVariablesFunctionParams": genServerURLWithVariablesFunctionParams, } diff --git a/pkg/codegen/templates/additional-properties.tmpl b/pkg/codegen/templates/additional-properties.tmpl index c83cc107ed..a103216c94 100644 --- a/pkg/codegen/templates/additional-properties.tmpl +++ b/pkg/codegen/templates/additional-properties.tmpl @@ -40,7 +40,7 @@ func (a *{{.TypeName}}) UnmarshalJSON(b []byte) error { var fieldVal {{$addType}} err := json.Unmarshal(fieldBuf, &fieldVal) if err != nil { - return fmt.Errorf("error unmarshalling field %s: %w", fieldName, err) + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) } a.AdditionalProperties[fieldName] = fieldVal } @@ -53,12 +53,12 @@ func (a {{.TypeName}}) MarshalJSON() ([]byte, error) { var err error object := make(map[string]json.RawMessage) {{range .Schema.Properties}} -{{if not .Required}}if a.{{.GoFieldName}} != nil { {{end}} +{{if .RequiresNilCheck}}if a.{{.GoFieldName}} != nil { {{end}} object["{{.JsonFieldName}}"], err = json.Marshal(a.{{.GoFieldName}}) if err != nil { return nil, fmt.Errorf("error marshaling '{{.JsonFieldName}}': %w", err) } -{{if not .Required}} }{{end}} +{{if .RequiresNilCheck}} }{{end}} {{end}} for fieldName, field := range a.AdditionalProperties { object[fieldName], err = json.Marshal(field) @@ -69,4 +69,4 @@ func (a {{.TypeName}}) MarshalJSON() ([]byte, error) { return json.Marshal(object) } {{end}} -{{end}} \ No newline at end of file +{{end}} diff --git a/pkg/codegen/templates/chi/chi-interface.tmpl b/pkg/codegen/templates/chi/chi-interface.tmpl index 79a51fd75b..cfc393bde2 100644 --- a/pkg/codegen/templates/chi/chi-interface.tmpl +++ b/pkg/codegen/templates/chi/chi-interface.tmpl @@ -5,3 +5,13 @@ type ServerInterface interface { {{.OperationId}}(w http.ResponseWriter, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) {{end}} } + +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct {} + {{range .}}{{.SummaryAsComment }} + // ({{.Method}} {{.Path}}) + func (_ Unimplemented) {{.OperationId}}(w http.ResponseWriter, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) { + w.WriteHeader(http.StatusNotImplemented) + } + {{end}} \ No newline at end of file diff --git a/pkg/codegen/templates/chi/chi-middleware.tmpl b/pkg/codegen/templates/chi/chi-middleware.tmpl index e99bbebdbd..c3e7cf82ae 100644 --- a/pkg/codegen/templates/chi/chi-middleware.tmpl +++ b/pkg/codegen/templates/chi/chi-middleware.tmpl @@ -11,7 +11,6 @@ type MiddlewareFunc func(http.Handler) http.Handler // {{$opid}} operation middleware func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() {{if or .RequiresParamObject (gt (len .PathParams) 0) }} var err error {{end}} @@ -25,12 +24,12 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ {{if .IsJson}} err = json.Unmarshal([]byte(chi.URLParam(r, "{{.ParamName}}")), &{{$varName}}) if err != nil { - siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) + siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) return } {{end}} {{if .IsStyled}} - err = runtime.BindStyledParameterWithLocation("{{.Style}}",{{.Explode}}, "{{.ParamName}}", runtime.ParamLocationPath, chi.URLParam(r, "{{.ParamName}}"), &{{$varName}}) + err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", chi.URLParam(r, "{{.ParamName}}"), &{{$varName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) return @@ -39,9 +38,13 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ {{end}} + {{if .SecurityDefinitions -}} + ctx := r.Context() {{range .SecurityDefinitions}} ctx = context.WithValue(ctx, {{.ProviderName | sanitizeGoIdentity | ucFirst}}Scopes, {{toStringArray .Scopes}}) {{end}} + r = r.WithContext(ctx) + {{end}} {{if .RequiresParamObject}} // Parameter object where we will unmarshal all parameters from the context @@ -55,18 +58,18 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" { {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue {{end}} {{if .IsJson}} var value {{.TypeDef}} err = json.Unmarshal([]byte(paramValue), &value) if err != nil { - siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) + siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} }{{if .Required}} else { siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"}) @@ -74,7 +77,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ }{{end}} {{end}} {{if .IsStyled}} - err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", r.URL.Query(), ¶ms.{{.GoName}}) + err = runtime.BindQueryParameterWithOptions("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", r.URL.Query(), ¶ms.{{.GoName}}, runtime.BindQueryParameterOptions{Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) return @@ -95,26 +98,26 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ } {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0] + params.{{.GoName}} = {{if .HasOptionalPointer }}&{{end}}valueList[0] {{end}} {{if .IsJson}} err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) if err != nil { - siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) + siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) return } {{end}} {{if .IsStyled}} - err = runtime.BindStyledParameterWithLocation("{{.Style}}",{{.Explode}}, "{{.ParamName}}", runtime.ParamLocationHeader, valueList[0], &{{.GoName}}) + err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", valueList[0], &{{.GoName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) return } {{end}} - params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}} } {{if .Required}}else { err := fmt.Errorf("Header parameter {{.ParamName}} is required, but not found") @@ -126,12 +129,13 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ {{end}} {{range .CookieParams}} + { var cookie *http.Cookie if cookie, err = r.Cookie("{{.ParamName}}"); err == nil { {{- if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}cookie.Value {{end}} {{- if .IsJson}} @@ -146,21 +150,21 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ err = json.Unmarshal([]byte(decoded), &value) if err != nil { - siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) + siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} {{- if .IsStyled}} var value {{.TypeDef}} - err = runtime.BindStyledParameter("simple",{{.Explode}}, "{{.ParamName}}", cookie.Value, &value) + err = runtime.BindStyledParameterWithOptions("simple", "{{.ParamName}}", cookie.Value, &value, runtime.BindStyledParameterOptions{Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} } @@ -170,12 +174,13 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ return } {{- end}} + } {{end}} {{end}} - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.{{.OperationId}}(w, r{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) - }) + })) {{if opts.Compatibility.ApplyChiMiddlewareFirstToLast}} for i := len(siw.HandlerMiddlewares) -1; i >= 0; i-- { @@ -187,7 +192,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ } {{end}} - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } {{end}} @@ -204,16 +209,16 @@ func (e *UnescapedCookieParamError) Unwrap() error { return e.Err } -type UnmarshallingParamError struct { +type UnmarshalingParamError struct { ParamName string Err error } -func (e *UnmarshallingParamError) Error() string { - return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) } -func (e *UnmarshallingParamError) Unwrap() error { +func (e *UnmarshalingParamError) Unwrap() error { return e.Err } diff --git a/pkg/codegen/templates/client-with-responses.tmpl b/pkg/codegen/templates/client-with-responses.tmpl index a3fd2b6dd5..3b85500a8d 100644 --- a/pkg/codegen/templates/client-with-responses.tmpl +++ b/pkg/codegen/templates/client-with-responses.tmpl @@ -33,7 +33,7 @@ type ClientWithResponsesInterface interface { {{$hasParams := .RequiresParamObject -}} {{$pathParams := .PathParams -}} {{$opid := .OperationId -}} - // {{$opid}} request{{if .HasBody}} with any body{{end}} + // {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse request{{if .HasBody}} with any body{{end}} {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error) {{range .Bodies}} {{if .IsSupportedByClient -}} @@ -44,14 +44,21 @@ type ClientWithResponsesInterface interface { } {{range .}}{{$opid := .OperationId}}{{$op := .}} +{{$responseTypeDefinitions := getResponseTypeDefinitions .}} type {{genResponseTypeName $opid | ucFirst}} struct { Body []byte HTTPResponse *http.Response - {{- range getResponseTypeDefinitions .}} + {{- range $responseTypeDefinitions}} {{.TypeName}} *{{.Schema.TypeDecl}} {{- end}} } +{{- range $responseTypeDefinitions}} + {{- range .AdditionalTypeDefinitions}} + type {{.TypeName}} {{if .IsAlias }}={{end}} {{.Schema.TypeDecl}} + {{- end}} +{{- end}} + // Status returns HTTPResponse.Status func (r {{genResponseTypeName $opid | ucFirst}}) Status() string { if r.HTTPResponse != nil { @@ -67,6 +74,13 @@ func (r {{genResponseTypeName $opid | ucFirst}}) StatusCode() int { } return 0 } + +{{ if opts.OutputOptions.ClientResponseBytesFunction }} +// Bytes is a convenience method to retrieve the raw bytes from the HTTP response +func (r {{genResponseTypeName $opid | ucFirst}}) Bytes() []byte { + return r.Body +} +{{end}} {{end}} diff --git a/pkg/codegen/templates/client.tmpl b/pkg/codegen/templates/client.tmpl index 58df443c76..24410dfae6 100644 --- a/pkg/codegen/templates/client.tmpl +++ b/pkg/codegen/templates/client.tmpl @@ -77,7 +77,7 @@ type ClientInterface interface { {{$hasParams := .RequiresParamObject -}} {{$pathParams := .PathParams -}} {{$opid := .OperationId -}} - // {{$opid}} request{{if .HasBody}} with any body{{end}} + // {{$opid}}{{if .HasBody}}WithBody{{end}} request{{if .HasBody}} with any body{{end}} {{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}, reqEditors... RequestEditorFn) (*http.Response, error) {{range .Bodies}} {{if .IsSupportedByClient -}} @@ -135,7 +135,7 @@ func (c *{{ $clientTypeName }}) {{$opid}}{{.Suffix}}(ctx context.Context{{genPar // New{{$opid}}Request{{.Suffix}} calls the generic {{$opid}} builder with {{.ContentType}} body func New{{$opid}}Request{{.Suffix}}(server string{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody) (*http.Request, error) { var bodyReader io.Reader - {{if eq .NameTag "JSON" -}} + {{if .IsJSON -}} buf, err := json.Marshal(body) if err != nil { return nil, err @@ -172,7 +172,7 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr pathParam{{$paramIdx}} = string(pathParamBuf{{$paramIdx}}) {{end}} {{if .IsStyled}} - pathParam{{$paramIdx}}, err = runtime.StyleParamWithLocation("{{.Style}}", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationPath, {{.GoVariableName}}) + pathParam{{$paramIdx}}, err = runtime.StyleParamWithOptions("{{.Style}}", {{.Explode}}, "{{.ParamName}}", {{.GoVariableName}}, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) if err != nil { return nil, err } @@ -194,36 +194,38 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr } {{if .QueryParams}} - queryValues := queryURL.Query() -{{range $paramIdx, $param := .QueryParams}} - {{if not .Required}} if params.{{.GoName}} != nil { {{end}} - {{if .IsPassThrough}} - queryValues.Add("{{.ParamName}}", {{if not .Required}}*{{end}}params.{{.GoName}}) - {{end}} - {{if .IsJson}} - if queryParamBuf, err := json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}}); err != nil { - return nil, err - } else { - queryValues.Add("{{.ParamName}}", string(queryParamBuf)) - } + if params != nil { + queryValues := queryURL.Query() + {{range $paramIdx, $param := .QueryParams}} + {{if .RequiresNilCheck}} if params.{{.GoName}} != nil { {{end}} + {{if .IsPassThrough}} + queryValues.Add("{{.ParamName}}", {{if .HasOptionalPointer}}*{{end}}params.{{.GoName}}) + {{end}} + {{if .IsJson}} + if queryParamBuf, err := json.Marshal({{if .HasOptionalPointer}}*{{end}}params.{{.GoName}}); err != nil { + return nil, err + } else { + queryValues.Add("{{.ParamName}}", string(queryParamBuf)) + } - {{end}} - {{if .IsStyled}} - if queryFrag, err := runtime.StyleParamWithLocation("{{.Style}}", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationQuery, {{if not .Required}}*{{end}}params.{{.GoName}}); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } + {{end}} + {{if .IsStyled}} + if queryFrag, err := runtime.StyleParamWithOptions("{{.Style}}", {{.Explode}}, "{{.ParamName}}", {{if and .RequiresNilCheck .HasOptionalPointer}}*{{end}}params.{{.GoName}}, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + {{end}} + {{if .RequiresNilCheck}}}{{end}} + {{end}} + queryURL.RawQuery = queryValues.Encode() } - {{end}} - {{if not .Required}}}{{end}} -{{end}} - queryURL.RawQuery = queryValues.Encode() {{end}}{{/* if .QueryParams */}} req, err := http.NewRequest("{{.Method}}", queryURL.String(), {{if .HasBody}}body{{else}}nil{{end}}) if err != nil { @@ -231,57 +233,65 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr } {{if .HasBody}}req.Header.Add("Content-Type", contentType){{end}} -{{range $paramIdx, $param := .HeaderParams}} - {{if not .Required}} if params.{{.GoName}} != nil { {{end}} - var headerParam{{$paramIdx}} string - {{if .IsPassThrough}} - headerParam{{$paramIdx}} = {{if not .Required}}*{{end}}params.{{.GoName}} +{{ if .HeaderParams }} + if params != nil { + {{range $paramIdx, $param := .HeaderParams}} + {{if .RequiresNilCheck}} if params.{{.GoName}} != nil { {{end}} + var headerParam{{$paramIdx}} string + {{if .IsPassThrough}} + headerParam{{$paramIdx}} = {{if .HasOptionalPointer}}*{{end}}params.{{.GoName}} + {{end}} + {{if .IsJson}} + var headerParamBuf{{$paramIdx}} []byte + headerParamBuf{{$paramIdx}}, err = json.Marshal({{if .HasOptionalPointer}}*{{end}}params.{{.GoName}}) + if err != nil { + return nil, err + } + headerParam{{$paramIdx}} = string(headerParamBuf{{$paramIdx}}) + {{end}} + {{if .IsStyled}} + headerParam{{$paramIdx}}, err = runtime.StyleParamWithOptions("{{.Style}}", {{.Explode}}, "{{.ParamName}}", {{if .HasOptionalPointer}}*{{end}}params.{{.GoName}}, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) + if err != nil { + return nil, err + } + {{end}} + req.Header.Set("{{.ParamName}}", headerParam{{$paramIdx}}) + {{if .RequiresNilCheck}}}{{end}} {{end}} - {{if .IsJson}} - var headerParamBuf{{$paramIdx}} []byte - headerParamBuf{{$paramIdx}}, err = json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}}) - if err != nil { - return nil, err } - headerParam{{$paramIdx}} = string(headerParamBuf{{$paramIdx}}) - {{end}} - {{if .IsStyled}} - headerParam{{$paramIdx}}, err = runtime.StyleParamWithLocation("{{.Style}}", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationHeader, {{if not .Required}}*{{end}}params.{{.GoName}}) - if err != nil { - return nil, err - } - {{end}} - req.Header.Set("{{.ParamName}}", headerParam{{$paramIdx}}) - {{if not .Required}}}{{end}} -{{end}} +{{- end }}{{/* if .HeaderParams */}} -{{range $paramIdx, $param := .CookieParams}} - {{if not .Required}} if params.{{.GoName}} != nil { {{end}} - var cookieParam{{$paramIdx}} string - {{if .IsPassThrough}} - cookieParam{{$paramIdx}} = {{if not .Required}}*{{end}}params.{{.GoName}} - {{end}} - {{if .IsJson}} - var cookieParamBuf{{$paramIdx}} []byte - cookieParamBuf{{$paramIdx}}, err = json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}}) - if err != nil { - return nil, err - } - cookieParam{{$paramIdx}} = url.QueryEscape(string(cookieParamBuf{{$paramIdx}})) - {{end}} - {{if .IsStyled}} - cookieParam{{$paramIdx}}, err = runtime.StyleParamWithLocation("simple", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationCookie, {{if not .Required}}*{{end}}params.{{.GoName}}) - if err != nil { - return nil, err - } - {{end}} - cookie{{$paramIdx}} := &http.Cookie{ - Name:"{{.ParamName}}", - Value:cookieParam{{$paramIdx}}, +{{ if .CookieParams }} + if params != nil { + {{range $paramIdx, $param := .CookieParams}} + {{if .RequiresNilCheck}} if params.{{.GoName}} != nil { {{end}} + var cookieParam{{$paramIdx}} string + {{if .IsPassThrough}} + cookieParam{{$paramIdx}} = {{if .HasOptionalPointer}}*{{end}}params.{{.GoName}} + {{end}} + {{if .IsJson}} + var cookieParamBuf{{$paramIdx}} []byte + cookieParamBuf{{$paramIdx}}, err = json.Marshal({{if .HasOptionalPointer}}*{{end}}params.{{.GoName}}) + if err != nil { + return nil, err + } + cookieParam{{$paramIdx}} = url.QueryEscape(string(cookieParamBuf{{$paramIdx}})) + {{end}} + {{if .IsStyled}} + cookieParam{{$paramIdx}}, err = runtime.StyleParamWithOptions("simple", {{.Explode}}, "{{.ParamName}}", {{if .HasOptionalPointer}}*{{end}}params.{{.GoName}}, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationCookie, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) + if err != nil { + return nil, err + } + {{end}} + cookie{{$paramIdx}} := &http.Cookie{ + Name:"{{.ParamName}}", + Value:cookieParam{{$paramIdx}}, + } + req.AddCookie(cookie{{$paramIdx}}) + {{if .RequiresNilCheck}}}{{end}} + {{ end -}} } - req.AddCookie(cookie{{$paramIdx}}) - {{if not .Required}}}{{end}} -{{end}} +{{- end }}{{/* if .CookieParams */}} return req, nil } diff --git a/pkg/codegen/templates/constants.tmpl b/pkg/codegen/templates/constants.tmpl index 8fad8764c4..1c8cbd4922 100644 --- a/pkg/codegen/templates/constants.tmpl +++ b/pkg/codegen/templates/constants.tmpl @@ -12,4 +12,14 @@ const ( {{$name}} {{$Enum.TypeName}} = {{$Enum.ValueWrapper}}{{$value}}{{$Enum.ValueWrapper -}} {{end}} ) + +// Valid indicates whether the value is a known member of the {{$Enum.TypeName}} enum. +func (e {{$Enum.TypeName}}) Valid() bool { + switch e { + {{range $name, $value := $Enum.GetValues}}case {{$name}}: + return true + {{end}}default: + return false + } +} {{end}} diff --git a/pkg/codegen/templates/echo/echo-wrappers.tmpl b/pkg/codegen/templates/echo/echo-wrappers.tmpl index f2e4dbc099..584a21f26c 100644 --- a/pkg/codegen/templates/echo/echo-wrappers.tmpl +++ b/pkg/codegen/templates/echo/echo-wrappers.tmpl @@ -14,11 +14,11 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error { {{if .IsJson}} err = json.Unmarshal([]byte(ctx.Param("{{.ParamName}}")), &{{$varName}}) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshalling parameter '{{.ParamName}}' as JSON") + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") } {{end}} {{if .IsStyled}} - err = runtime.BindStyledParameterWithLocation("{{.Style}}",{{.Explode}}, "{{.ParamName}}", runtime.ParamLocationPath, ctx.Param("{{.ParamName}}"), &{{$varName}}) + err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", ctx.Param("{{.ParamName}}"), &{{$varName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) } @@ -37,22 +37,22 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error { // ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" ------------- {{ end }} {{if .IsStyled}} - err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", ctx.QueryParams(), ¶ms.{{.GoName}}) + err = runtime.BindQueryParameterWithOptions("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", ctx.QueryParams(), ¶ms.{{.GoName}}, runtime.BindQueryParameterOptions{Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) } {{else}} if paramValue := ctx.QueryParam("{{.ParamName}}"); paramValue != "" { {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue {{end}} {{if .IsJson}} var value {{.TypeDef}} err = json.Unmarshal([]byte(paramValue), &value) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshalling parameter '{{.ParamName}}' as JSON") + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} }{{if .Required}} else { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Query argument {{.ParamName}} is required, but not found")) @@ -70,21 +70,21 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for {{.ParamName}}, got %d", n)) } {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0] + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}valueList[0] {{end}} {{if .IsJson}} err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshalling parameter '{{.ParamName}}' as JSON") + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") } {{end}} {{if .IsStyled}} - err = runtime.BindStyledParameterWithLocation("{{.Style}}",{{.Explode}}, "{{.ParamName}}", runtime.ParamLocationHeader, valueList[0], &{{.GoName}}) + err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", valueList[0], &{{.GoName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) } {{end}} - params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}} } {{if .Required}}else { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Header parameter {{.ParamName}} is required, but not found")) }{{end}} @@ -94,7 +94,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error { {{range .CookieParams}} if cookie, err := ctx.Cookie("{{.ParamName}}"); err == nil { {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}cookie.Value {{end}} {{if .IsJson}} var value {{.TypeDef}} @@ -105,17 +105,17 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error { } err = json.Unmarshal([]byte(decoded), &value) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshalling parameter '{{.ParamName}}' as JSON") + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} {{if .IsStyled}} var value {{.TypeDef}} - err = runtime.BindStyledParameterWithLocation("simple",{{.Explode}}, "{{.ParamName}}", runtime.ParamLocationCookie, cookie.Value, &value) + err = runtime.BindStyledParameterWithOptions("simple", "{{.ParamName}}", cookie.Value, &value, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationCookie, Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} }{{if .Required}} else { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Query argument {{.ParamName}} is required, but not found")) @@ -124,7 +124,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error { {{end}}{{/* .CookieParams */}} {{end}}{{/* .RequiresParamObject */}} - // Invoke the callback with all the unmarshalled arguments + // Invoke the callback with all the unmarshaled arguments err = w.Handler.{{.OperationId}}(ctx{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) return err } diff --git a/pkg/codegen/templates/echo/v5/echo-interface.tmpl b/pkg/codegen/templates/echo/v5/echo-interface.tmpl new file mode 100644 index 0000000000..1531eef866 --- /dev/null +++ b/pkg/codegen/templates/echo/v5/echo-interface.tmpl @@ -0,0 +1,7 @@ +// ServerInterface represents all server handlers. +type ServerInterface interface { +{{range .}}{{.SummaryAsComment }} +// ({{.Method}} {{.Path}}) +{{.OperationId}}(ctx *echo.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) error +{{end}} +} diff --git a/pkg/codegen/templates/echo/v5/echo-register.tmpl b/pkg/codegen/templates/echo/v5/echo-register.tmpl new file mode 100644 index 0000000000..78de21b83e --- /dev/null +++ b/pkg/codegen/templates/echo/v5/echo-register.tmpl @@ -0,0 +1,33 @@ + + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) echo.RouteInfo + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) echo.RouteInfo + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) echo.RouteInfo + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) echo.RouteInfo + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) echo.RouteInfo + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) echo.RouteInfo + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) echo.RouteInfo + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) echo.RouteInfo + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) echo.RouteInfo +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { +{{if .}} + wrapper := ServerInterfaceWrapper{ + Handler: si, + } +{{end}} +{{range .}}router.{{.Method}}(baseURL + "{{.Path | swaggerUriToEchoUri}}", wrapper.{{.OperationId}}) +{{end}} +} diff --git a/pkg/codegen/templates/echo/v5/echo-wrappers.tmpl b/pkg/codegen/templates/echo/v5/echo-wrappers.tmpl new file mode 100644 index 0000000000..9c1856167c --- /dev/null +++ b/pkg/codegen/templates/echo/v5/echo-wrappers.tmpl @@ -0,0 +1,131 @@ +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +{{range .}}{{$opid := .OperationId}}// {{$opid}} converts echo context to params. +func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx *echo.Context) error { + var err error +{{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" ------------- + var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}} +{{if .IsPassThrough}} + {{$varName}} = ctx.Param("{{.ParamName}}") +{{end}} +{{if .IsJson}} + err = json.Unmarshal([]byte(ctx.Param("{{.ParamName}}")), &{{$varName}}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") + } +{{end}} +{{if .IsStyled}} + err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", ctx.Param("{{.ParamName}}"), &{{$varName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: {{.Explode}}, Required: {{.Required}}}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) + } +{{end}} +{{end}} + +{{range .SecurityDefinitions}} + ctx.Set({{.ProviderName | sanitizeGoIdentity | ucFirst}}Scopes, {{toStringArray .Scopes}}) +{{end}} + +{{if .RequiresParamObject}} + // Parameter object where we will unmarshal all parameters from the context + var params {{.OperationId}}Params +{{range $paramIdx, $param := .QueryParams}} + {{- if (or (or .Required .IsPassThrough) (or .IsJson .IsStyled)) -}} + // ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" ------------- + {{ end }} + {{if .IsStyled}} + err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", ctx.QueryParams(), ¶ms.{{.GoName}}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) + } + {{else}} + if paramValue := ctx.QueryParam("{{.ParamName}}"); paramValue != "" { + {{if .IsPassThrough}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue + {{end}} + {{if .IsJson}} + var value {{.TypeDef}} + err = json.Unmarshal([]byte(paramValue), &value) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") + } + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value + {{end}} + }{{if .Required}} else { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Query argument {{.ParamName}} is required, but not found")) + }{{end}} + {{end}} +{{end}} + +{{if .HeaderParams}} + headers := ctx.Request().Header +{{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found { + var {{.GoName}} {{.TypeDef}} + n := len(valueList) + if n != 1 { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for {{.ParamName}}, got %d", n)) + } +{{if .IsPassThrough}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}valueList[0] +{{end}} +{{if .IsJson}} + err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") + } +{{end}} +{{if .IsStyled}} + err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", valueList[0], &{{.GoName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: {{.Explode}}, Required: {{.Required}}}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) + } +{{end}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}} + } {{if .Required}}else { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Header parameter {{.ParamName}} is required, but not found")) + }{{end}} +{{end}} +{{end}} + +{{range .CookieParams}} + if cookie, err := ctx.Cookie("{{.ParamName}}"); err == nil { + {{if .IsPassThrough}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}cookie.Value + {{end}} + {{if .IsJson}} + var value {{.TypeDef}} + var decoded string + decoded, err := url.QueryUnescape(cookie.Value) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Error unescaping cookie parameter '{{.ParamName}}'") + } + err = json.Unmarshal([]byte(decoded), &value) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") + } + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value + {{end}} + {{if .IsStyled}} + var value {{.TypeDef}} + err = runtime.BindStyledParameterWithOptions("simple", "{{.ParamName}}", cookie.Value, &value, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationCookie, Explode: {{.Explode}}, Required: {{.Required}}}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) + } + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value + {{end}} + }{{if .Required}} else { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Query argument {{.ParamName}} is required, but not found")) + }{{end}} + +{{end}}{{/* .CookieParams */}} + +{{end}}{{/* .RequiresParamObject */}} + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.{{.OperationId}}(ctx{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) + return err +} +{{end}} diff --git a/pkg/codegen/templates/fiber/fiber-handler.tmpl b/pkg/codegen/templates/fiber/fiber-handler.tmpl new file mode 100644 index 0000000000..7745e0d88e --- /dev/null +++ b/pkg/codegen/templates/fiber/fiber-handler.tmpl @@ -0,0 +1,25 @@ +// FiberServerOptions provides options for the Fiber server. +type FiberServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router fiber.Router, si ServerInterface) { + RegisterHandlersWithOptions(router, si, FiberServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router fiber.Router, si ServerInterface, options FiberServerOptions) { +{{if .}}wrapper := ServerInterfaceWrapper{ +Handler: si, +} + +for _, m := range options.Middlewares { + router.Use(fiber.Handler(m)) +} +{{end}} +{{range .}} +router.{{.Method | lower | title }}(options.BaseURL+"{{.Path | swaggerUriToFiberUri}}", wrapper.{{.OperationId}}) +{{end}} +} diff --git a/pkg/codegen/templates/fiber/fiber-interface.tmpl b/pkg/codegen/templates/fiber/fiber-interface.tmpl new file mode 100644 index 0000000000..8ef90a851a --- /dev/null +++ b/pkg/codegen/templates/fiber/fiber-interface.tmpl @@ -0,0 +1,7 @@ +// ServerInterface represents all server handlers. +type ServerInterface interface { +{{range .}}{{.SummaryAsComment }} +// ({{.Method}} {{.Path}}) +{{.OperationId}}(c *fiber.Ctx{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) error +{{end}} +} diff --git a/pkg/codegen/templates/fiber/fiber-middleware.tmpl b/pkg/codegen/templates/fiber/fiber-middleware.tmpl new file mode 100644 index 0000000000..63bc2cb831 --- /dev/null +++ b/pkg/codegen/templates/fiber/fiber-middleware.tmpl @@ -0,0 +1,173 @@ +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +type MiddlewareFunc fiber.Handler + +{{range .}}{{$opid := .OperationId}} + +// {{$opid}} operation middleware +func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) error { + + {{if or .RequiresParamObject (gt (len .PathParams) 0) }} + var err error + {{end}} + + {{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" ------------- + var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}} + + {{if .IsPassThrough}} + {{$varName}} = c.Query("{{.ParamName}}") + {{end}} + {{if .IsJson}} + err = json.Unmarshal([]byte(c.Query("{{.ParamName}}")), &{{$varName}}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Error unmarshaling parameter '{{.ParamName}}' as JSON: %w", err).Error()) + } + {{end}} + {{if .IsStyled}} + err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", c.Params("{{.ParamName}}"), &{{$varName}}, runtime.BindStyledParameterOptions{Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err).Error()) + } + {{end}} + + {{end}} + +{{range .SecurityDefinitions}} + c.Context().SetUserValue({{.ProviderName | ucFirst}}Scopes, {{toStringArray .Scopes}}) +{{end}} + + {{if .RequiresParamObject}} + // Parameter object where we will unmarshal all parameters from the context + var params {{.OperationId}}Params + + {{if .QueryParams}} + var query url.Values + query, err = url.ParseQuery(string(c.Request().URI().QueryString())) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for query string: %w", err).Error()) + } + {{end}} + + {{range $paramIdx, $param := .QueryParams}} + {{- if (or (or .Required .IsPassThrough) (or .IsJson .IsStyled)) -}} + // ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" ------------- + {{ end }} + {{ if (or (or .Required .IsPassThrough) .IsJson) }} + if paramValue := c.Query("{{.ParamName}}"); paramValue != "" { + + {{if .IsPassThrough}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue + {{end}} + + {{if .IsJson}} + var value {{.TypeDef}} + err = json.Unmarshal([]byte(paramValue), &value) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Error unmarshaling parameter '{{.ParamName}}' as JSON: %w", err).Error()) + } + + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value + {{end}} + }{{if .Required}} else { + err = fmt.Errorf("Query argument {{.ParamName}} is required, but not found") + c.Status(fiber.StatusBadRequest).JSON(err) + return err + }{{end}} + {{end}} + {{if .IsStyled}} + err = runtime.BindQueryParameterWithOptions("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", query, ¶ms.{{.GoName}}, runtime.BindQueryParameterOptions{Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err).Error()) + } + {{end}} + {{end}} + + {{if .HeaderParams}} + headers := c.GetReqHeaders() + + {{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found { + var {{.GoName}} {{.TypeDef}} + n := len(valueList) + if n != 1 { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Too many values for ParamName {{.ParamName}}, 1 is required, but %d found", n)) + } + + {{if .IsPassThrough}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}valueList[0] + {{end}} + + {{if .IsJson}} + err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Error unmarshaling parameter '{{.ParamName}}' as JSON: %w", err).Error()) + } + {{end}} + + {{if .IsStyled}} + err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", valueList[0], &{{.GoName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err).Error()) + } + {{end}} + + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}} + + } {{if .Required}}else { + err = fmt.Errorf("Header parameter {{.ParamName}} is required, but not found: %w", err) + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + }{{end}} + + {{end}} + {{end}} + + {{range .CookieParams}} + var cookie string + + if cookie = c.Cookies("{{.ParamName}}"); cookie == "" { + + {{- if .IsPassThrough}} + params.{{.GoName}} = {{if .HasOptionalPointer}}}&{{end}}cookie + {{end}} + + {{- if .IsJson}} + var value {{.TypeDef}} + var decoded string + decoded, err := url.QueryUnescape(cookie) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Error unescaping cookie parameter '{{.ParamName}}': %w", err).Error()) + } + + err = json.Unmarshal([]byte(decoded), &value) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Error unmarshaling parameter '{{.ParamName}}' as JSON: %w", err).Error()) + } + + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value + {{end}} + + {{- if .IsStyled}} + var value {{.TypeDef}} + err = runtime.BindStyledParameterWithOptions("simple", "{{.ParamName}}", cookie, &value, runtime.BindStyledParameterOptions{Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err).Error()) + } + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value + {{end}} + + } + + {{- if .Required}} else { + err = fmt.Errorf("Query argument {{.ParamName}} is required, but not found") + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + {{- end}} + {{end}} + {{end}} + + return siw.Handler.{{.OperationId}}(c{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) +} +{{end}} diff --git a/pkg/codegen/templates/gin/gin-wrappers.tmpl b/pkg/codegen/templates/gin/gin-wrappers.tmpl index c34a522873..272e2ec609 100644 --- a/pkg/codegen/templates/gin/gin-wrappers.tmpl +++ b/pkg/codegen/templates/gin/gin-wrappers.tmpl @@ -25,14 +25,14 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { {{if .IsJson}} err = json.Unmarshal([]byte(c.Query("{{.ParamName}}")), &{{$varName}}) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Error unmarshalling parameter '{{.ParamName}}' as JSON"), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Error unmarshaling parameter '{{.ParamName}}' as JSON"), http.StatusBadRequest) return } {{end}} {{if .IsStyled}} - err = runtime.BindStyledParameter("{{.Style}}",{{.Explode}}, "{{.ParamName}}", c.Param("{{.ParamName}}"), &{{$varName}}) + err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", c.Param("{{.ParamName}}"), &{{$varName}}, runtime.BindStyledParameterOptions{Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err), http.StatusBadRequest) return } {{end}} @@ -55,29 +55,29 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { if paramValue := c.Query("{{.ParamName}}"); paramValue != "" { {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue {{end}} {{if .IsJson}} var value {{.TypeDef}} err = json.Unmarshal([]byte(paramValue), &value) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Error unmarshalling parameter '{{.ParamName}}' as JSON: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Error unmarshaling parameter '{{.ParamName}}' as JSON: %w", err), http.StatusBadRequest) return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} }{{if .Required}} else { - siw.ErrorHandler(c, fmt.Errorf("Query argument {{.ParamName}} is required, but not found: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Query argument {{.ParamName}} is required, but not found"), http.StatusBadRequest) return }{{end}} {{end}} {{if .IsStyled}} - err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", c.Request.URL.Query(), ¶ms.{{.GoName}}) + err = runtime.BindQueryParameterWithOptions("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", c.Request.URL.Query(), ¶ms.{{.GoName}}, runtime.BindQueryParameterOptions{Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err), http.StatusBadRequest) return } {{end}} @@ -96,29 +96,29 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { } {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0] + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}valueList[0] {{end}} {{if .IsJson}} err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Error unmarshalling parameter '{{.ParamName}}' as JSON"), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Error unmarshaling parameter '{{.ParamName}}' as JSON"), http.StatusBadRequest) return } {{end}} {{if .IsStyled}} - err = runtime.BindStyledParameterWithLocation("{{.Style}}",{{.Explode}}, "{{.ParamName}}", runtime.ParamLocationHeader, valueList[0], &{{.GoName}}) + err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", valueList[0], &{{.GoName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err), http.StatusBadRequest) return } {{end}} - params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}} } {{if .Required}}else { - siw.ErrorHandler(c, fmt.Errorf("Header parameter {{.ParamName}} is required, but not found: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Header parameter {{.ParamName}} is required, but not found"), http.StatusBadRequest) return }{{end}} @@ -132,7 +132,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { if cookie, err = c.Cookie("{{.ParamName}}"); err == nil { {{- if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}cookie + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}cookie {{end}} {{- if .IsJson}} @@ -146,21 +146,21 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { err = json.Unmarshal([]byte(decoded), &value) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Error unmarshalling parameter '{{.ParamName}}' as JSON"), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Error unmarshaling parameter '{{.ParamName}}' as JSON"), http.StatusBadRequest) return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} {{- if .IsStyled}} var value {{.TypeDef}} - err = runtime.BindStyledParameter("simple",{{.Explode}}, "{{.ParamName}}", cookie, &value) + err = runtime.BindStyledParameterWithOptions("simple", "{{.ParamName}}", cookie, &value, runtime.BindStyledParameterOptions{Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err), http.StatusBadRequest) return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} } diff --git a/pkg/codegen/templates/gorilla/gorilla-middleware.tmpl b/pkg/codegen/templates/gorilla/gorilla-middleware.tmpl index 1360891137..6a1b18ce2d 100644 --- a/pkg/codegen/templates/gorilla/gorilla-middleware.tmpl +++ b/pkg/codegen/templates/gorilla/gorilla-middleware.tmpl @@ -5,13 +5,12 @@ type ServerInterfaceWrapper struct { ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) } -type MiddlewareFunc func(http.HandlerFunc) http.HandlerFunc +type MiddlewareFunc func(http.Handler) http.Handler {{range .}}{{$opid := .OperationId}} // {{$opid}} operation middleware func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() {{if or .RequiresParamObject (gt (len .PathParams) 0) }} var err error {{end}} @@ -25,12 +24,12 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ {{if .IsJson}} err = json.Unmarshal([]byte(mux.Vars(r)["{{.ParamName}}"]), &{{$varName}}) if err != nil { - siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) + siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) return } {{end}} {{if .IsStyled}} - err = runtime.BindStyledParameter("{{.Style}}",{{.Explode}}, "{{.ParamName}}", mux.Vars(r)["{{.ParamName}}"], &{{$varName}}) + err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", mux.Vars(r)["{{.ParamName}}"], &{{$varName}}, runtime.BindStyledParameterOptions{Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) return @@ -39,9 +38,13 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ {{end}} + {{if .SecurityDefinitions -}} + ctx := r.Context() {{range .SecurityDefinitions}} ctx = context.WithValue(ctx, {{.ProviderName | sanitizeGoIdentity | ucFirst}}Scopes, {{toStringArray .Scopes}}) {{end}} + r = r.WithContext(ctx) + {{end}} {{if .RequiresParamObject}} // Parameter object where we will unmarshal all parameters from the context @@ -55,18 +58,18 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" { {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue {{end}} {{if .IsJson}} var value {{.TypeDef}} err = json.Unmarshal([]byte(paramValue), &value) if err != nil { - siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) + siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} }{{if .Required}} else { siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"}) @@ -74,7 +77,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ }{{end}} {{end}} {{if .IsStyled}} - err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", r.URL.Query(), ¶ms.{{.GoName}}) + err = runtime.BindQueryParameterWithOptions("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", r.URL.Query(), ¶ms.{{.GoName}}, runtime.BindQueryParameterOptions{Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) return @@ -95,26 +98,26 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ } {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0] + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}valueList[0] {{end}} {{if .IsJson}} err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) if err != nil { - siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) + siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) return } {{end}} {{if .IsStyled}} - err = runtime.BindStyledParameterWithLocation("{{.Style}}",{{.Explode}}, "{{.ParamName}}", runtime.ParamLocationHeader, valueList[0], &{{.GoName}}) + err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", valueList[0], &{{.GoName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) return } {{end}} - params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}} } {{if .Required}}else { err = fmt.Errorf("Header parameter {{.ParamName}} is required, but not found") @@ -126,12 +129,13 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ {{end}} {{range .CookieParams}} + { var cookie *http.Cookie if cookie, err = r.Cookie("{{.ParamName}}"); err == nil { {{- if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}cookie.Value {{end}} {{- if .IsJson}} @@ -146,21 +150,21 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ err = json.Unmarshal([]byte(decoded), &value) if err != nil { - siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) + siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} {{- if .IsStyled}} var value {{.TypeDef}} - err = runtime.BindStyledParameter("simple",{{.Explode}}, "{{.ParamName}}", cookie.Value, &value) + err = runtime.BindStyledParameterWithOptions("simple", "{{.ParamName}}", cookie.Value, &value, runtime.BindStyledParameterOptions{Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} } @@ -170,12 +174,13 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ return } {{- end}} + } {{end}} {{end}} - var handler = func(w http.ResponseWriter, r *http.Request) { + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.{{.OperationId}}(w, r{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) -} + })) {{if opts.Compatibility.ApplyGorillaMiddlewareFirstToLast}} for i := len(siw.HandlerMiddlewares) -1; i >= 0; i-- { @@ -187,7 +192,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ } {{end}} - handler(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r) } {{end}} @@ -204,16 +209,16 @@ func (e *UnescapedCookieParamError) Unwrap() error { return e.Err } -type UnmarshallingParamError struct { +type UnmarshalingParamError struct { ParamName string Err error } -func (e *UnmarshallingParamError) Error() string { - return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) } -func (e *UnmarshallingParamError) Unwrap() error { +func (e *UnmarshalingParamError) Unwrap() error { return e.Err } diff --git a/pkg/codegen/templates/imports.tmpl b/pkg/codegen/templates/imports.tmpl index ef43ef390f..8c76dad36d 100644 --- a/pkg/codegen/templates/imports.tmpl +++ b/pkg/codegen/templates/imports.tmpl @@ -1,3 +1,6 @@ +{{- if opts.Generate.StdHTTPServer}}//go:build go1.22 + +{{- end}} // Package {{.PackageName}} provides primitives to interact with the openapi HTTP API. // // Code generated by {{.ModuleName}} version {{.Version}} DO NOT EDIT. @@ -15,19 +18,21 @@ import ( "gopkg.in/yaml.v2" "io" "os" + "mime" + "mime/multipart" "net/http" "net/url" "path" "strings" "time" - "github.com/deepmap/oapi-codegen/pkg/runtime" - openapi_types "github.com/deepmap/oapi-codegen/pkg/types" + "github.com/oapi-codegen/runtime" + "github.com/oapi-codegen/nullable" + openapi_types "github.com/oapi-codegen/runtime/types" "github.com/getkin/kin-openapi/openapi3" - "github.com/go-chi/chi/v5" - "github.com/labstack/echo/v4" - "github.com/gin-gonic/gin" - "github.com/gorilla/mux" + {{- range .RouterImports}} + {{if .Alias}}{{.Alias}} {{end}}"{{.Package}}" + {{- end}} {{- range .ExternalImports}} {{ . }} {{- end}} diff --git a/pkg/codegen/templates/inline.tmpl b/pkg/codegen/templates/inline.tmpl index 106a42b247..de00120502 100644 --- a/pkg/codegen/templates/inline.tmpl +++ b/pkg/codegen/templates/inline.tmpl @@ -9,16 +9,16 @@ var swaggerSpec = []string{ func decodeSpec() ([]byte, error) { zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) + return nil, fmt.Errorf("error base64 decoding spec: %w", err) } zr, err := gzip.NewReader(bytes.NewReader(zipped)) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } var buf bytes.Buffer _, err = buf.ReadFrom(zr) if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + return nil, fmt.Errorf("error decompressing spec: %w", err) } return buf.Bytes(), nil @@ -36,21 +36,18 @@ func decodeSpecCached() func() ([]byte, error) { // Constructs a synthetic filesystem for resolving external references when loading openapi specifications. func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) + res := make(map[string]func() ([]byte, error)) if len(pathToFile) > 0 { res[pathToFile] = rawSpec } - {{ if .ImportMapping }} - pathPrefix := path.Dir(pathToFile) - {{ end }} - {{ range $key, $value := .ImportMapping }} - for rawPath, rawFunc := range {{ $value.Name }}.PathToRawSpec(path.Join(pathPrefix, "{{ $key }}")) { + {{ range $key, $value := .ImportMapping }}{{- if ne $value.Path "-"}} + for rawPath, rawFunc := range {{ $value.Name }}.PathToRawSpec(path.Join(path.Dir(pathToFile), "{{ $key }}")) { if _, ok := res[rawPath]; ok { // it is not possible to compare functions in golang, so always overwrite the old value } res[rawPath] = rawFunc } - {{- end }} + {{- end }}{{- end }} return res } @@ -60,12 +57,12 @@ func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { // Externally referenced files must be embedded in the corresponding golang packages. // Urls can be supported but this task was out of the scope. func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") + resolvePath := PathToRawSpec("") loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() + pathToFile := url.String() pathToFile = path.Clean(pathToFile) getSpec, ok := resolvePath[pathToFile] if !ok { diff --git a/pkg/codegen/templates/iris/iris-handler.tmpl b/pkg/codegen/templates/iris/iris-handler.tmpl new file mode 100644 index 0000000000..3f1228faea --- /dev/null +++ b/pkg/codegen/templates/iris/iris-handler.tmpl @@ -0,0 +1,23 @@ +// IrisServerOption is the option for iris server +type IrisServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router *iris.Application, si ServerInterface) { + RegisterHandlersWithOptions(router, si, IrisServerOptions{}) +} + + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router *iris.Application, si ServerInterface, options IrisServerOptions) { +{{if .}} + wrapper := ServerInterfaceWrapper{ + Handler: si, + } +{{end}} +{{range .}}router.{{.Method | lower | title}}(options.BaseURL + "{{.Path | swaggerUriToIrisUri}}", wrapper.{{.OperationId}}) +{{end}} + router.Build() +} diff --git a/pkg/codegen/templates/iris/iris-interface.tmpl b/pkg/codegen/templates/iris/iris-interface.tmpl new file mode 100644 index 0000000000..fb53a124b9 --- /dev/null +++ b/pkg/codegen/templates/iris/iris-interface.tmpl @@ -0,0 +1,7 @@ +// ServerInterface represents all server handlers. +type ServerInterface interface { +{{range .}}{{.SummaryAsComment }} +// ({{.Method}} {{.Path}}) +{{.OperationId}}(ctx iris.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) +{{end}} +} diff --git a/pkg/codegen/templates/iris/iris-middleware.tmpl b/pkg/codegen/templates/iris/iris-middleware.tmpl new file mode 100644 index 0000000000..0bc6b1229b --- /dev/null +++ b/pkg/codegen/templates/iris/iris-middleware.tmpl @@ -0,0 +1,161 @@ +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +type MiddlewareFunc iris.Handler + +{{range .}}{{$opid := .OperationId}}// {{$opid}} converts iris context to params. +func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx iris.Context) { +{{if or .RequiresParamObject (gt (len .PathParams) 0) }} + var err error +{{end}} + +{{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" ------------- + var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}} +{{if .IsPassThrough}} + {{$varName}} = ctx.URLParam("{{.ParamName}}") +{{end}} +{{if .IsJson}} + err = json.Unmarshal([]byte(ctx.URLParam("{{.ParamName}}")), &{{$varName}}) + if err != nil { + ctx.StatusCode(http.StatusBadRequest) + ctx.WriteString("Error unmarshaling parameter '{{.ParamName}}' as JSON") + return + } +{{end}} +{{if .IsStyled}} + err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", ctx.Params().Get("{{.ParamName}}"), &{{$varName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) + if err != nil { + ctx.StatusCode(http.StatusBadRequest) + ctx.Writef("Invalid format for parameter {{.ParamName}}: %s", err) + return + } +{{end}} +{{end}} + +{{range .SecurityDefinitions}} + ctx.Set({{.ProviderName | sanitizeGoIdentity | ucFirst}}Scopes, {{toStringArray .Scopes}}) +{{end}} + +{{if .RequiresParamObject}} + // Parameter object where we will unmarshal all parameters from the context + var params {{.OperationId}}Params +{{range $paramIdx, $param := .QueryParams}} + {{- if (or (or .Required .IsPassThrough) (or .IsJson .IsStyled)) -}} + // ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" ------------- + {{ end }} + {{if .IsStyled}} + err = runtime.BindQueryParameterWithOptions("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", ctx.Request().URL.Query(), ¶ms.{{.GoName}}, runtime.BindQueryParameterOptions{Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) + if err != nil { + ctx.StatusCode(http.StatusBadRequest) + ctx.Writef("Invalid format for parameter {{.ParamName}}: %s", err) + return + } + {{else}} + if paramValue := ctx.QueryParam("{{.ParamName}}"); paramValue != "" { + {{if .IsPassThrough}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue + {{end}} + {{if .IsJson}} + var value {{.TypeDef}} + err = json.Unmarshal([]byte(paramValue), &value) + if err != nil { + ctx.StatusCode(http.StatusBadRequest) + ctx.WriteString("Error unmarshaling parameter '{{.ParamName}}' as JSON") + return + } + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value + {{end}} + }{{if .Required}} else { + ctx.StatusCode(http.StatusBadRequest) + ctx.WriteString("Query argument {{.ParamName}} is required, but not found") + return + }{{end}} + {{end}} +{{end}} + +{{if .HeaderParams}} + headers := ctx.Request().Header +{{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found { + var {{.GoName}} {{.TypeDef}} + n := len(valueList) + if n != 1 { + ctx.StatusCode(http.StatusBadRequest) + ctx.Writef("Expected one value for {{.ParamName}}, got %d", n) + return + } +{{if .IsPassThrough}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}valueList[0] +{{end}} +{{if .IsJson}} + err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) + if err != nil { + ctx.StatusCode(http.StatusBadRequest) + ctx.WriteString("Error unmarshaling parameter '{{.ParamName}}' as JSON") + return + } +{{end}} +{{if .IsStyled}} + err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", valueList[0], &{{.GoName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) + if err != nil { + ctx.StatusCode(http.StatusBadRequest) + ctx.Writef("Invalid format for parameter {{.ParamName}}: %s", err) + return + } +{{end}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}} + } {{if .Required}}else { + ctx.StatusCode(http.StatusBadRequest) + ctx.WriteString("Header {{.ParamName}} is required, but not found") + return + }{{end}} +{{end}} +{{end}} + +{{range .CookieParams}} + if cookie, err := ctx.Cookie("{{.ParamName}}"); err == nil { + {{if .IsPassThrough}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}cookie.Value + {{end}} + {{if .IsJson}} + var value {{.TypeDef}} + var decoded string + decoded, err := url.QueryUnescape(cookie.Value) + if err != nil { + ctx.StatusCode(http.StatusBadRequest) + ctx.WriteString("Error unescaping cookie parameter '{{.ParamName}}'") + return + } + err = json.Unmarshal([]byte(decoded), &value) + if err != nil { + ctx.StatusCode(http.StatusBadRequest) + ctx.WriteString("Error unmarshaling parameter '{{.ParamName}}' as JSON") + return + } + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value + {{end}} + {{if .IsStyled}} + var value {{.TypeDef}} + err = runtime.BindStyledParameterWithOptions("simple", "{{.ParamName}}", cookie.Value, &value, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationCookie, Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) + if err != nil { + ctx.StatusCode(http.StatusBadRequest) + ctx.Writef("Invalid format for parameter {{.ParamName}}: %s", err) + return + } + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value + {{end}} + }{{if .Required}} else { + ctx.StatusCode(http.StatusBadRequest) + ctx.WriteString("Cookie {{.ParamName}} is required, but not found") + return + }{{end}} + +{{end}}{{/* .CookieParams */}} + +{{end}}{{/* .RequiresParamObject */}} + // Invoke the callback with all the unmarshaled arguments + w.Handler.{{.OperationId}}(ctx{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) +} +{{end}} diff --git a/pkg/codegen/templates/server-urls.tmpl b/pkg/codegen/templates/server-urls.tmpl new file mode 100644 index 0000000000..3f1fdfe734 --- /dev/null +++ b/pkg/codegen/templates/server-urls.tmpl @@ -0,0 +1,61 @@ +{{ range . }} +{{ if eq 0 (len .OAPISchema.Variables) }} +{{/* URLs without variables are straightforward, so we'll create them a constant */}} +// {{ .GoName }} defines the Server URL for {{ if len .OAPISchema.Description }}{{ .OAPISchema.Description }}{{ else }}{{ .OAPISchema.URL }}{{ end }} +const {{ .GoName}} = "{{ .OAPISchema.URL }}" +{{ else }} +{{/* URLs with variables are not straightforward, as we may need multiple types, and so will model them as a function */}} + +{{/* first, we'll start by generating requisite types */}} + +{{ $goName := .GoName }} +{{ range $k, $v := .OAPISchema.Variables }} + {{ $prefix := printf "%s%sVariable" $goName ($k | ucFirst) }} + // {{ $prefix }} is the `{{ $k }}` variable for {{ $goName }} + type {{ $prefix }} string + {{ range $v.Enum }} + {{/* TODO this may result in broken generated code if any of the `enum` values are the literal value `default` https://github.com/oapi-codegen/oapi-codegen/issues/2003 */}} + // {{ $prefix }}{{ . | ucFirst }} is one of the accepted values for the `{{ $k }}` variable for {{ $goName }} + const {{ $prefix }}{{ . | ucFirst }} {{ $prefix }} = "{{ . }}" + {{ end }} + + {{/* TODO we should introduce a `Valid() error` method to enums https://github.com/oapi-codegen/oapi-codegen/issues/2006 */}} + + {{ if $v.Default }} + {{ if gt (len $v.Enum) 0 }} + {{/* if we have an enum, we should use the type defined for it for its default value + and reference the constant we've already defined for the value */}} + {{/* TODO this may result in broken generated code if any of the `enum` values are the literal value `default` https://github.com/oapi-codegen/oapi-codegen/issues/2003 */}} + {{/* TODO this may result in broken generated code if the `default` isn't found in `enum` (which is an issue with the spec) https://github.com/oapi-codegen/oapi-codegen/issues/2007 */}} + // {{ $prefix }}Default is the default choice, for the accepted values for the `{{ $k }}` variable for {{ $goName }} + const {{ $prefix }}Default {{ $prefix }} = {{ $prefix }}{{ $v.Default | ucFirst }} + {{ else }} + // {{ $prefix }}Default is the default value for the `{{ $k }}` variable for {{ $goName }} + const {{ $prefix }}Default = "{{ $v.Default }}" + {{ end }} + {{ end }} +{{ end }} + + +// New{{ .GoName }} constructs the Server URL for {{ .OAPISchema.Description }}, with the provided variables. +func New{{ .GoName }}({{ genServerURLWithVariablesFunctionParams .GoName .OAPISchema.Variables }}) (string, error) { + u := "{{ .OAPISchema.URL }}" + + {{ range $k, $v := .OAPISchema.Variables }} + {{- $placeholder := printf "{%s}" $k -}} + {{- if gt (len $v.Enum) 0 -}} + {{/* TODO https://github.com/oapi-codegen/oapi-codegen/issues/2006 */}} + // TODO in the future, this will validate that the value is part of the {{ printf "%s%sVariable" $goName ($k | ucFirst) }} enum + {{ end -}} + u = strings.ReplaceAll(u, "{{ $placeholder }}", string({{ $k }})) + {{ end }} + + if strings.Contains(u, "{") || strings.Contains(u, "}") { + return "", fmt.Errorf("after mapping variables, there were still `{` or `}` characters in the string: %#v", u) + } + + return u, nil +} + +{{ end }} +{{ end }} diff --git a/pkg/codegen/templates/stdhttp/std-http-handler.tmpl b/pkg/codegen/templates/stdhttp/std-http-handler.tmpl new file mode 100644 index 0000000000..bed4685c74 --- /dev/null +++ b/pkg/codegen/templates/stdhttp/std-http-handler.tmpl @@ -0,0 +1,55 @@ +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{}) +} + +// ServeMux is an abstraction of http.ServeMux. +type ServeMux interface { + HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) + ServeHTTP(w http.ResponseWriter, r *http.Request) +} + +type StdHTTPServerOptions struct { + BaseURL string + BaseRouter ServeMux + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, m ServeMux) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions { + BaseRouter: m, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, m ServeMux, baseURL string) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions { + BaseURL: baseURL, + BaseRouter: m, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.Handler { + m := options.BaseRouter + + if m == nil { + m = http.NewServeMux() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } +{{if .}} + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } +{{end}} +{{range .}}m.HandleFunc("{{.Method }} "+options.BaseURL+"{{.Path | swaggerUriToStdHttpUri}}", wrapper.{{.OperationId}}) +{{end}} + return m +} diff --git a/pkg/codegen/templates/stdhttp/std-http-interface.tmpl b/pkg/codegen/templates/stdhttp/std-http-interface.tmpl new file mode 100644 index 0000000000..79a51fd75b --- /dev/null +++ b/pkg/codegen/templates/stdhttp/std-http-interface.tmpl @@ -0,0 +1,7 @@ +// ServerInterface represents all server handlers. +type ServerInterface interface { +{{range .}}{{.SummaryAsComment }} +// ({{.Method}} {{.Path}}) +{{.OperationId}}(w http.ResponseWriter, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) +{{end}} +} diff --git a/pkg/codegen/templates/stdhttp/std-http-middleware.tmpl b/pkg/codegen/templates/stdhttp/std-http-middleware.tmpl new file mode 100644 index 0000000000..fbccc40a03 --- /dev/null +++ b/pkg/codegen/templates/stdhttp/std-http-middleware.tmpl @@ -0,0 +1,266 @@ +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +{{range .}}{{$opid := .OperationId}} + +// {{$opid}} operation middleware +func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Request) { + {{if or .RequiresParamObject (gt (len .PathParams) 0) }} + var err error + {{end}} + + {{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" ------------- + var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}} + + {{if .IsPassThrough}} + {{$varName}} = r.PathValue("{{.ParamName}}") + {{end}} + {{if .IsJson}} + err = json.Unmarshal([]byte(r.PathValue("{{.ParamName}}")), &{{$varName}}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) + return + } + {{end}} + {{if .IsStyled}} + err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", r.PathValue("{{.ParamName}}"), &{{$varName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) + return + } + {{end}} + + {{end}} + + {{if .SecurityDefinitions -}} + ctx := r.Context() +{{range .SecurityDefinitions}} + ctx = context.WithValue(ctx, {{.ProviderName | sanitizeGoIdentity | ucFirst}}Scopes, {{toStringArray .Scopes}}) +{{end}} + r = r.WithContext(ctx) + {{end}} + + {{if .RequiresParamObject}} + // Parameter object where we will unmarshal all parameters from the context + var params {{.OperationId}}Params + + {{range $paramIdx, $param := .QueryParams}} + {{- if (or (or .Required .IsPassThrough) (or .IsJson .IsStyled)) -}} + // ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" ------------- + {{ end }} + {{ if (or (or .Required .IsPassThrough) .IsJson) }} + if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" { + + {{if .IsPassThrough}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue + {{end}} + + {{if .IsJson}} + var value {{.TypeDef}} + err = json.Unmarshal([]byte(paramValue), &value) + if err != nil { + siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) + return + } + + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value + {{end}} + }{{if .Required}} else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"}) + return + }{{end}} + {{end}} + {{if .IsStyled}} + err = runtime.BindQueryParameterWithOptions("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", r.URL.Query(), ¶ms.{{.GoName}}, runtime.BindQueryParameterOptions{Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) + return + } + {{end}} + {{end}} + + {{if .HeaderParams}} + headers := r.Header + + {{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found { + var {{.GoName}} {{.TypeDef}} + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "{{.ParamName}}", Count: n}) + return + } + + {{if .IsPassThrough}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}valueList[0] + {{end}} + + {{if .IsJson}} + err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) + return + } + {{end}} + + {{if .IsStyled}} + err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", valueList[0], &{{.GoName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) + return + } + {{end}} + + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}} + + } {{if .Required}}else { + err := fmt.Errorf("Header parameter {{.ParamName}} is required, but not found") + siw.ErrorHandlerFunc(w, r, &RequiredHeaderError{ParamName: "{{.ParamName}}", Err: err}) + return + }{{end}} + + {{end}} + {{end}} + + {{range .CookieParams}} + { + var cookie *http.Cookie + + if cookie, err = r.Cookie("{{.ParamName}}"); err == nil { + + {{- if .IsPassThrough}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}cookie.Value + {{end}} + + {{- if .IsJson}} + var value {{.TypeDef}} + var decoded string + decoded, err := url.QueryUnescape(cookie.Value) + if err != nil { + err = fmt.Errorf("Error unescaping cookie parameter '{{.ParamName}}'") + siw.ErrorHandlerFunc(w, r, &UnescapedCookieParamError{ParamName: "{{.ParamName}}", Err: err}) + return + } + + err = json.Unmarshal([]byte(decoded), &value) + if err != nil { + siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) + return + } + + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value + {{end}} + + {{- if .IsStyled}} + var value {{.TypeDef}} + err = runtime.BindStyledParameterWithOptions("simple", "{{.ParamName}}", cookie.Value, &value, runtime.BindStyledParameterOptions{Explode: {{.Explode}}, Required: {{.Required}}, Type: "{{.SchemaType}}", Format: "{{.SchemaFormat}}"}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) + return + } + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value + {{end}} + + } + + {{- if .Required}} else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"}) + return + } + {{- end}} + } + {{end}} + {{end}} + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.{{.OperationId}}(w, r{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) + })) + + {{if opts.Compatibility.ApplyChiMiddlewareFirstToLast}} + for i := len(siw.HandlerMiddlewares) -1; i >= 0; i-- { + handler = siw.HandlerMiddlewares[i](handler) + } + {{else}} + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + {{end}} + + handler.ServeHTTP(w, r) +} +{{end}} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} diff --git a/pkg/codegen/templates/strict/strict-echo.tmpl b/pkg/codegen/templates/strict/strict-echo.tmpl index c858c09f8e..7b470c8ce7 100644 --- a/pkg/codegen/templates/strict/strict-echo.tmpl +++ b/pkg/codegen/templates/strict/strict-echo.tmpl @@ -1,6 +1,5 @@ -type StrictHandlerFunc func(ctx echo.Context, args interface{}) (interface{}, error) - -type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc +type StrictHandlerFunc = strictecho.StrictEchoHandlerFunc +type StrictMiddlewareFunc = strictecho.StrictEchoMiddlewareFunc func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { return &strictHandler{ssi: ssi, middlewares: middlewares} @@ -18,8 +17,7 @@ type strictHandler struct { var request {{$opid | ucFirst}}RequestObject {{range .PathParams -}} - {{$varName := .GoVariableName -}} - request.{{$varName | ucFirst}} = {{$varName}} + request.{{.GoName}} = {{.GoVariableName}} {{end -}} {{if .RequiresParamObject -}} @@ -33,12 +31,19 @@ type strictHandler struct { {{$multipleBodies := gt (len .Bodies) 1 -}} {{range .Bodies -}} {{if $multipleBodies}}if strings.HasPrefix(ctx.Request().Header.Get("Content-Type"), "{{.ContentType}}") { {{end}} - {{if eq .NameTag "JSON" -}} + {{if .IsJSON -}} var body {{$opid}}{{.NameTag}}RequestBody if err := ctx.Bind(&body); err != nil { + {{if not .Required -}} + if !errors.Is(err, io.EOF) { + return err + } + {{else -}} return err - } + {{end -}} + } {{if not .Required -}} else { {{end}} request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{if not .Required -}} } {{end}} {{else if eq .NameTag "Formdata" -}} if form, err := ctx.FormParams(); err == nil { var body {{$opid}}{{.NameTag}}RequestBody @@ -50,18 +55,34 @@ type strictHandler struct { return err } {{else if eq .NameTag "Multipart" -}} + {{if eq .ContentType "multipart/form-data" -}} if reader, err := ctx.Request().MultipartReader(); err != nil { return err } else { request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = reader } + {{else -}} + if _, params, err := mime.ParseMediaType(ctx.Request().Header.Get("Content-Type")); err != nil { + return err + } else if boundary := params["boundary"]; boundary == "" { + return http.ErrMissingBoundary + } else { + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = multipart.NewReader(ctx.Request().Body, boundary) + } + {{end -}} {{else if eq .NameTag "Text" -}} data, err := io.ReadAll(ctx.Request().Body) if err != nil { return err } + {{if not .Required -}} + if len(data) > 0 { + {{end -}} body := {{$opid}}{{.NameTag}}RequestBody(data) request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{if not .Required -}} + } + {{end -}} {{else -}} request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = ctx.Request().Body {{end}}{{/* if eq .NameTag "JSON" */ -}} @@ -82,7 +103,7 @@ type strictHandler struct { } else if validResponse, ok := response.({{$opid | ucFirst}}ResponseObject); ok { return validResponse.Visit{{$opid}}Response(ctx.Response()) } else if response != nil { - return fmt.Errorf("Unexpected response type: %T", response) + return fmt.Errorf("unexpected response type: %T", response) } return nil } diff --git a/pkg/codegen/templates/strict/strict-echo5.tmpl b/pkg/codegen/templates/strict/strict-echo5.tmpl new file mode 100644 index 0000000000..1f377bfdab --- /dev/null +++ b/pkg/codegen/templates/strict/strict-echo5.tmpl @@ -0,0 +1,97 @@ +type StrictHandlerFunc = strictecho5.StrictEchoHandlerFunc +type StrictMiddlewareFunc = strictecho5.StrictEchoMiddlewareFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +{{range .}} + {{$opid := .OperationId}} + // {{$opid}} operation middleware + func (sh *strictHandler) {{.OperationId}}(ctx *echo.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) error { + var request {{$opid | ucFirst}}RequestObject + + {{range .PathParams -}} + request.{{.GoName}} = {{.GoVariableName}} + {{end -}} + + {{if .RequiresParamObject -}} + request.Params = params + {{end -}} + + {{ if .HasMaskedRequestContentTypes -}} + request.ContentType = ctx.Request().Header.Get("Content-Type") + {{end -}} + + {{$multipleBodies := gt (len .Bodies) 1 -}} + {{range .Bodies -}} + {{if $multipleBodies}}if strings.HasPrefix(ctx.Request().Header.Get("Content-Type"), "{{.ContentType}}") { {{end}} + {{if .IsJSON -}} + var body {{$opid}}{{.NameTag}}RequestBody + if err := ctx.Bind(&body); err != nil { + return err + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else if eq .NameTag "Formdata" -}} + if form, err := ctx.FormParams(); err == nil { + var body {{$opid}}{{.NameTag}}RequestBody + if err := runtime.BindForm(&body, form, nil, nil); err != nil { + return err + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + } else { + return err + } + {{else if eq .NameTag "Multipart" -}} + {{if eq .ContentType "multipart/form-data" -}} + if reader, err := ctx.Request().MultipartReader(); err != nil { + return err + } else { + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = reader + } + {{else -}} + if _, params, err := mime.ParseMediaType(ctx.Request().Header.Get("Content-Type")); err != nil { + return err + } else if boundary := params["boundary"]; boundary == "" { + return http.ErrMissingBoundary + } else { + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = multipart.NewReader(ctx.Request().Body, boundary) + } + {{end -}} + {{else if eq .NameTag "Text" -}} + data, err := io.ReadAll(ctx.Request().Body) + if err != nil { + return err + } + body := {{$opid}}{{.NameTag}}RequestBody(data) + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else -}} + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = ctx.Request().Body + {{end}}{{/* if eq .NameTag "JSON" */ -}} + {{if $multipleBodies}}}{{end}} + {{end}}{{/* range .Bodies */}} + + handler := func(ctx *echo.Context, request interface{}) (interface{}, error){ + return sh.ssi.{{.OperationId}}(ctx.Request().Context(), request.({{$opid | ucFirst}}RequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "{{.OperationId}}") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.({{$opid | ucFirst}}ResponseObject); ok { + return validResponse.Visit{{$opid}}Response(ctx.Response()) + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil + } +{{end}} diff --git a/pkg/codegen/templates/strict/strict-fiber-interface.tmpl b/pkg/codegen/templates/strict/strict-fiber-interface.tmpl new file mode 100644 index 0000000000..e83f7e38aa --- /dev/null +++ b/pkg/codegen/templates/strict/strict-fiber-interface.tmpl @@ -0,0 +1,145 @@ +{{range .}} + {{$opid := .OperationId -}} + type {{$opid | ucFirst}}RequestObject struct { + {{range .PathParams -}} + {{.GoName | ucFirst}} {{.TypeDef}} {{.JsonTag}} + {{end -}} + {{if .RequiresParamObject -}} + Params {{$opid}}Params + {{end -}} + {{if .HasMaskedRequestContentTypes -}} + ContentType string + {{end -}} + {{$multipleBodies := gt (len .Bodies) 1 -}} + {{range .Bodies -}} + {{if $multipleBodies}}{{.NameTag}}{{end}}Body {{if eq .NameTag "Multipart"}}*multipart.Reader{{else if ne .NameTag ""}}*{{$opid}}{{.NameTag}}RequestBody{{else}}io.Reader{{end}} + {{end -}} + } + + type {{$opid | ucFirst}}ResponseObject interface { + Visit{{$opid}}Response(ctx *fiber.Ctx) error + } + + {{range .Responses}} + {{$statusCode := .StatusCode -}} + {{$hasHeaders := ne 0 (len .Headers) -}} + {{$fixedStatusCode := .HasFixedStatusCode -}} + {{$isRef := .IsRef -}} + {{$isExternalRef := .IsExternalRef -}} + {{$ref := .Ref | ucFirst -}} + {{$headers := .Headers -}} + + {{if (and $hasHeaders (not $isRef)) -}} + type {{$opid}}{{$statusCode}}ResponseHeaders struct { + {{range .Headers -}} + {{.GoName}} {{.Schema.TypeDecl}} + {{end -}} + } + {{end}} + + {{range .Contents}} + {{$receiverTypeName := printf "%s%s%s%s" $opid $statusCode .NameTagOrContentType "Response"}} + {{if and $fixedStatusCode $isRef -}} + {{ if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) (eq .NameTag "Multipart") -}} + type {{$receiverTypeName}} {{$ref}}{{.NameTagOrContentType}}Response + {{else if $isExternalRef -}} + type {{$receiverTypeName}} struct { {{$ref}} } + {{else -}} + type {{$receiverTypeName}} struct{ {{$ref}}{{.NameTagOrContentType}}Response } + {{end}} + {{else if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) -}} + type {{$receiverTypeName}} {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{if and .Schema.IsRef (not .Schema.IsExternalRef)}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}} + {{else -}} + type {{$receiverTypeName}} struct { + Body {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{.Schema.TypeDecl}}{{else}}io.Reader{{end}} + {{if $hasHeaders -}} + Headers {{if $isRef}}{{$ref}}{{else}}{{$opid}}{{$statusCode}}{{end}}ResponseHeaders + {{end -}} + + {{if not $fixedStatusCode -}} + StatusCode int + {{end -}} + + {{if not .HasFixedContentType -}} + ContentType string + {{end -}} + + {{if not .IsSupported -}} + ContentLength int64 + {{end -}} + } + {{end}} + + func (response {{$receiverTypeName}}) Visit{{$opid}}Response(ctx *fiber.Ctx) error { + {{range $headers -}} + ctx.Response().Header.Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}})) + {{end -}} + {{if eq .NameTag "Multipart" -}} + writer := multipart.NewWriter(ctx.Response().BodyWriter()) + {{end -}} + ctx.Response().Header.Set("Content-Type", {{if eq .NameTag "Multipart"}}{{if eq .ContentType "multipart/form-data"}}writer.FormDataContentType(){{else}}mime.FormatMediaType("{{.ContentType}}", map[string]string{"boundary": writer.Boundary()}){{end}}{{else if .HasFixedContentType }}{{.ContentType | toGoString}}{{else}}response.ContentType{{end}}) + {{if not .IsSupported -}} + if response.ContentLength != 0 { + ctx.Response().Header.Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + {{end -}} + ctx.Status({{if $fixedStatusCode}}{{$statusCode}}{{else}}response.StatusCode{{end}}) + {{$hasBodyVar := or ($hasHeaders) (not $fixedStatusCode) (not .IsSupported)}} + {{if .IsJSON }} + {{$hasUnionElements := ne 0 (len .Schema.UnionElements)}} + return ctx.JSON(&{{if $hasBodyVar}}response.Body{{else}}response{{end}}{{if $hasUnionElements}}.union{{end}}) + {{else if eq .NameTag "Text" -}} + _, err := ctx.WriteString(string({{if $hasBodyVar}}response.Body{{else}}response{{end}})) + return err + {{else if eq .NameTag "Formdata" -}} + if form, err := runtime.MarshalForm({{if $hasBodyVar}}response.Body{{else}}response{{end}}, nil); err != nil { + return err + } else { + _, err := ctx.WriteString(form.Encode()) + return err + } + {{else if eq .NameTag "Multipart" -}} + defer writer.Close() + return {{if $hasBodyVar}}response.Body{{else}}response{{end}}(writer); + {{else -}} + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(ctx.Response().BodyWriter(), response.Body) + return err + {{end}}{{/* if eq .NameTag "JSON" */ -}} + } + {{end}} + + {{if eq 0 (len .Contents) -}} + {{if and $fixedStatusCode $isRef -}} + type {{$opid}}{{$statusCode}}Response {{if not $isExternalRef}}={{end}} {{$ref}}Response + {{else -}} + type {{$opid}}{{$statusCode}}Response struct { + {{if $hasHeaders -}} + Headers {{if $isRef}}{{$ref}}{{else}}{{$opid}}{{$statusCode}}{{end}}ResponseHeaders + {{end}} + {{if not $fixedStatusCode -}} + StatusCode int + {{end -}} + } + {{end -}} + func (response {{$opid}}{{$statusCode}}Response) Visit{{$opid}}Response(ctx *fiber.Ctx) error { + {{range $headers -}} + ctx.Response().Header.Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}})) + {{end -}} + ctx.Status({{if $fixedStatusCode}}{{$statusCode}}{{else}}response.StatusCode{{end}}) + return nil + } + {{end}} + {{end}} +{{end}} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { +{{range .}}{{.SummaryAsComment }} +// ({{.Method}} {{.Path}}) +{{$opid := .OperationId -}} +{{$opid}}(ctx context.Context, request {{$opid | ucFirst}}RequestObject) ({{$opid | ucFirst}}ResponseObject, error) +{{end}}{{/* range . */ -}} +} diff --git a/pkg/codegen/templates/strict/strict-fiber.tmpl b/pkg/codegen/templates/strict/strict-fiber.tmpl new file mode 100644 index 0000000000..59a2c0736b --- /dev/null +++ b/pkg/codegen/templates/strict/strict-fiber.tmpl @@ -0,0 +1,103 @@ +type StrictHandlerFunc func(ctx *fiber.Ctx, args interface{}) (interface{}, error) + +type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +{{range .}} + {{$opid := .OperationId}} + // {{$opid}} operation middleware + func (sh *strictHandler) {{.OperationId}}(ctx *fiber.Ctx{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) error { + var request {{$opid | ucFirst}}RequestObject + + {{range .PathParams -}} + {{$varName := .GoVariableName -}} + request.{{.GoName}} = {{.GoVariableName}} + {{end -}} + + {{if .RequiresParamObject -}} + request.Params = params + {{end -}} + + {{ if .HasMaskedRequestContentTypes -}} + request.ContentType = string(ctx.Request().Header.ContentType()) + {{end -}} + + {{$multipleBodies := gt (len .Bodies) 1 -}} + {{range .Bodies -}} + {{if $multipleBodies}}if strings.HasPrefix(string(ctx.Request().Header.ContentType()), "{{.ContentType}}") { {{end}} + {{if .IsJSON }} + var body {{$opid}}{{.NameTag}}RequestBody + if err := ctx.BodyParser(&body); err != nil { + {{if not .Required -}} + if !errors.Is(err, io.EOF) { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + {{else -}} + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + {{end -}} + } {{if not .Required -}} else { {{end}} + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{if not .Required -}} } {{end}} + {{else if eq .NameTag "Formdata" -}} + var body {{$opid}}{{.NameTag}}RequestBody + if err := ctx.BodyParser(&body); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else if eq .NameTag "Multipart" -}} + {{if eq .ContentType "multipart/form-data" -}} + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = multipart.NewReader(bytes.NewReader(ctx.Request().Body()), string(ctx.Request().Header.MultipartFormBoundary())) + {{else -}} + if _, params, err := mime.ParseMediaType(string(ctx.Request().Header.ContentType())); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if boundary := params["boundary"]; boundary == "" { + return fiber.NewError(fiber.StatusBadRequest, http.ErrMissingBoundary.Error()) + } else { + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = multipart.NewReader(bytes.NewReader(ctx.Request().Body()), boundary) + } + {{end -}} + {{else if eq .NameTag "Text" -}} + data := ctx.Request().Body() + {{if not .Required -}} + if len(data) > 0 { + {{end -}} + body := {{$opid}}{{.NameTag}}RequestBody(data) + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{if not .Required -}} + } + {{end -}} + {{else -}} + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = bytes.NewReader(ctx.Request().Body()) + {{end}}{{/* if eq .NameTag "JSON" */ -}} + {{if $multipleBodies}}}{{end}} + {{end}}{{/* range .Bodies */}} + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.{{.OperationId}}(ctx.UserContext(), request.({{$opid | ucFirst}}RequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "{{.OperationId}}") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.({{$opid | ucFirst}}ResponseObject); ok { + if err := validResponse.Visit{{$opid}}Response(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil + } +{{end}} diff --git a/pkg/codegen/templates/strict/strict-gin.tmpl b/pkg/codegen/templates/strict/strict-gin.tmpl index 8097e2801c..40a7b7bcb1 100644 --- a/pkg/codegen/templates/strict/strict-gin.tmpl +++ b/pkg/codegen/templates/strict/strict-gin.tmpl @@ -1,6 +1,5 @@ -type StrictHandlerFunc func(ctx *gin.Context, args interface{}) (interface{}, error) - -type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc +type StrictHandlerFunc = strictgin.StrictGinHandlerFunc +type StrictMiddlewareFunc = strictgin.StrictGinMiddlewareFunc func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { return &strictHandler{ssi: ssi, middlewares: middlewares} @@ -18,8 +17,7 @@ type strictHandler struct { var request {{$opid | ucFirst}}RequestObject {{range .PathParams -}} - {{$varName := .GoVariableName -}} - request.{{$varName | ucFirst}} = {{$varName}} + request.{{.GoName}} = {{.GoVariableName}} {{end -}} {{if .RequiresParamObject -}} @@ -33,14 +31,23 @@ type strictHandler struct { {{$multipleBodies := gt (len .Bodies) 1 -}} {{range .Bodies -}} {{if $multipleBodies}}if strings.HasPrefix(ctx.GetHeader("Content-Type"), "{{.ContentType}}") { {{end}} - {{if eq .NameTag "JSON" -}} + {{if .IsJSON }} var body {{$opid}}{{.NameTag}}RequestBody - if err := ctx.ShouldBind(&body); err != nil { + if err := ctx.ShouldBindJSON(&body); err != nil { + {{if not .Required -}} + if !errors.Is(err, io.EOF) { + ctx.Status(http.StatusBadRequest) + ctx.Error(err) + return + } + {{else -}} ctx.Status(http.StatusBadRequest) ctx.Error(err) return - } + {{end -}} + } {{if not .Required -}} else { {{end}} request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{if not .Required -}} } {{end}} {{else if eq .NameTag "Formdata" -}} if err := ctx.Request.ParseForm(); err != nil { ctx.Error(err) @@ -53,20 +60,38 @@ type strictHandler struct { } request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body {{else if eq .NameTag "Multipart" -}} + {{if eq .ContentType "multipart/form-data" -}} if reader, err := ctx.Request.MultipartReader(); err == nil { request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = reader } else { ctx.Error(err) return } + {{else -}} + if _, params, err := mime.ParseMediaType(ctx.Request.Header.Get("Content-Type")); err != nil { + ctx.Error(err) + return + } else if boundary := params["boundary"]; boundary == "" { + ctx.Error(http.ErrMissingBoundary) + return + } else { + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = multipart.NewReader(ctx.Request.Body, boundary) + } + {{end -}} {{else if eq .NameTag "Text" -}} data, err := io.ReadAll(ctx.Request.Body) if err != nil { ctx.Error(err) return } + {{if not .Required -}} + if len(data) > 0 { + {{end -}} body := {{$opid}}{{.NameTag}}RequestBody(data) request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{if not .Required -}} + } + {{end -}} {{else -}} request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = ctx.Request.Body {{end}}{{/* if eq .NameTag "JSON" */ -}} @@ -84,12 +109,13 @@ type strictHandler struct { if err != nil { ctx.Error(err) + ctx.Status(http.StatusInternalServerError) } else if validResponse, ok := response.({{$opid | ucFirst}}ResponseObject); ok { if err := validResponse.Visit{{$opid}}Response(ctx.Writer); err != nil { ctx.Error(err) } } else if response != nil { - ctx.Error(fmt.Errorf("Unexpected response type: %T", response)) + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) } } {{end}} diff --git a/pkg/codegen/templates/strict/strict-http.tmpl b/pkg/codegen/templates/strict/strict-http.tmpl index b1d2e9dba4..7314c26fd7 100644 --- a/pkg/codegen/templates/strict/strict-http.tmpl +++ b/pkg/codegen/templates/strict/strict-http.tmpl @@ -1,6 +1,5 @@ -type StrictHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, args interface{}) (interface{}, error) - -type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc +type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc +type StrictMiddlewareFunc = strictnethttp.StrictHTTPMiddlewareFunc type StrictHTTPServerOptions struct { RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) @@ -35,8 +34,7 @@ type strictHandler struct { var request {{$opid | ucFirst}}RequestObject {{range .PathParams -}} - {{$varName := .GoVariableName -}} - request.{{$varName | ucFirst}} = {{$varName}} + request.{{.GoName}} = {{.GoVariableName}} {{end -}} {{if .RequiresParamObject -}} @@ -50,13 +48,21 @@ type strictHandler struct { {{$multipleBodies := gt (len .Bodies) 1 -}} {{range .Bodies -}} {{if $multipleBodies}}if strings.HasPrefix(r.Header.Get("Content-Type"), "{{.ContentType}}") { {{end}} - {{if eq .NameTag "JSON" -}} + {{if .IsJSON }} var body {{$opid}}{{.NameTag}}RequestBody if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + {{if not .Required -}} + if !errors.Is(err, io.EOF) { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + {{else -}} sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) return - } + {{end -}} + } {{if not .Required -}} else { {{end}} request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{if not .Required -}} } {{end}} {{else if eq .NameTag "Formdata" -}} if err := r.ParseForm(); err != nil { sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode formdata: %w", err)) @@ -69,20 +75,38 @@ type strictHandler struct { } request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body {{else if eq .NameTag "Multipart" -}} + {{if eq .ContentType "multipart/form-data" -}} if reader, err := r.MultipartReader(); err != nil { sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode multipart body: %w", err)) return } else { request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = reader } + {{else -}} + if _, params, err := mime.ParseMediaType(r.Header.Get("Content-Type")); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, err) + return + } else if boundary := params["boundary"]; boundary == "" { + sh.options.RequestErrorHandlerFunc(w, r, http.ErrMissingBoundary) + return + } else { + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = multipart.NewReader(r.Body, boundary) + } + {{end -}} {{else if eq .NameTag "Text" -}} data, err := io.ReadAll(r.Body) if err != nil { sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't read body: %w", err)) return } + {{if not .Required -}} + if len(data) > 0 { + {{end -}} body := {{$opid}}{{.NameTag}}RequestBody(data) request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{if not .Required -}} + } + {{end -}} {{else -}} request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = r.Body {{end}}{{/* if eq .NameTag "JSON" */ -}} @@ -105,7 +129,7 @@ type strictHandler struct { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) } } -{{end}} \ No newline at end of file +{{end}} diff --git a/pkg/codegen/templates/strict/strict-interface.tmpl b/pkg/codegen/templates/strict/strict-interface.tmpl index 1911f56e8d..62f3d4d9c6 100644 --- a/pkg/codegen/templates/strict/strict-interface.tmpl +++ b/pkg/codegen/templates/strict/strict-interface.tmpl @@ -25,6 +25,7 @@ {{$hasHeaders := ne 0 (len .Headers) -}} {{$fixedStatusCode := .HasFixedStatusCode -}} {{$isRef := .IsRef -}} + {{$isExternalRef := .IsExternalRef -}} {{$ref := .Ref | ucFirstWithPkgName -}} {{$headers := .Headers -}} @@ -39,9 +40,15 @@ {{range .Contents}} {{$receiverTypeName := printf "%s%s%s%s" $opid $statusCode .NameTagOrContentType "Response"}} {{if and $fixedStatusCode $isRef -}} + {{ if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) (or (eq .NameTag "Multipart") (eq .NameTag "Text")) -}} + type {{$receiverTypeName}} {{$ref}}{{.NameTagOrContentType}}Response + {{else if $isExternalRef -}} + type {{$receiverTypeName}} struct { {{$ref}} } + {{else -}} type {{$receiverTypeName}} struct{ {{$ref}}{{.NameTagOrContentType}}Response } + {{end}} {{else if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) -}} - type {{$receiverTypeName}} {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{if .Schema.IsRef}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}} + type {{$receiverTypeName}} {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{if and .Schema.IsRef (not .Schema.IsExternalRef)}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}} {{else -}} type {{$receiverTypeName}} struct { Body {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{.Schema.TypeDecl}}{{else}}io.Reader{{end}} @@ -64,22 +71,23 @@ {{end}} func (response {{$receiverTypeName}}) Visit{{$opid}}Response(w http.ResponseWriter) error { - {{range $headers -}} - w.Header().Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}})) - {{end -}} {{if eq .NameTag "Multipart" -}} writer := multipart.NewWriter(w) {{end -}} - w.Header().Set("Content-Type", {{if eq .NameTag "Multipart"}}writer.FormDataContentType(){{else if .HasFixedContentType }}"{{.ContentType}}"{{else}}response.ContentType{{end}}) + w.Header().Set("Content-Type", {{if eq .NameTag "Multipart"}}{{if eq .ContentType "multipart/form-data"}}writer.FormDataContentType(){{else}}mime.FormatMediaType("{{.ContentType}}", map[string]string{"boundary": writer.Boundary()}){{end}}{{else if .HasFixedContentType }}{{.ContentType | toGoString}}{{else}}response.ContentType{{end}}) {{if not .IsSupported -}} if response.ContentLength != 0 { w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) } {{end -}} + {{range $headers -}} + w.Header().Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}})) + {{end -}} w.WriteHeader({{if $fixedStatusCode}}{{$statusCode}}{{else}}response.StatusCode{{end}}) {{$hasBodyVar := or ($hasHeaders) (not $fixedStatusCode) (not .IsSupported)}} - {{if eq .NameTag "JSON" -}} - return json.NewEncoder(w).Encode({{if $hasBodyVar}}response.Body{{else}}response{{end}}) + {{if .IsJSON -}} + {{$hasUnionElements := ne 0 (len .Schema.UnionElements)}} + return json.NewEncoder(w).Encode(response{{if $hasBodyVar}}.Body{{end}}{{if $hasUnionElements}}.union{{end}}) {{else if eq .NameTag "Text" -}} _, err := w.Write([]byte({{if $hasBodyVar}}response.Body{{else}}response{{end}})) return err @@ -105,7 +113,7 @@ {{if eq 0 (len .Contents) -}} {{if and $fixedStatusCode $isRef -}} - type {{$opid}}{{$statusCode}}Response = {{$ref}}Response + type {{$opid}}{{$statusCode}}Response {{if not $isExternalRef}}={{end}} {{$ref}}Response {{else -}} type {{$opid}}{{$statusCode}}Response struct { {{if $hasHeaders -}} diff --git a/pkg/codegen/templates/strict/strict-iris-interface.tmpl b/pkg/codegen/templates/strict/strict-iris-interface.tmpl new file mode 100644 index 0000000000..95b78d0645 --- /dev/null +++ b/pkg/codegen/templates/strict/strict-iris-interface.tmpl @@ -0,0 +1,147 @@ +{{range .}} + {{$opid := .OperationId -}} + type {{$opid | ucFirst}}RequestObject struct { + {{range .PathParams -}} + {{.GoName | ucFirst}} {{.TypeDef}} {{.JsonTag}} + {{end -}} + {{if .RequiresParamObject -}} + Params {{$opid}}Params + {{end -}} + {{if .HasMaskedRequestContentTypes -}} + ContentType string + {{end -}} + {{$multipleBodies := gt (len .Bodies) 1 -}} + {{range .Bodies -}} + {{if $multipleBodies}}{{.NameTag}}{{end}}Body {{if eq .NameTag "Multipart"}}*multipart.Reader{{else if ne .NameTag ""}}*{{$opid}}{{.NameTag}}RequestBody{{else}}io.Reader{{end}} + {{end -}} + } + + type {{$opid | ucFirst}}ResponseObject interface { + Visit{{$opid}}Response(ctx iris.Context) error + } + + {{range .Responses}} + {{$statusCode := .StatusCode -}} + {{$hasHeaders := ne 0 (len .Headers) -}} + {{$fixedStatusCode := .HasFixedStatusCode -}} + {{$isRef := .IsRef -}} + {{$isExternalRef := .IsExternalRef -}} + {{$ref := .Ref | ucFirstWithPkgName -}} + {{$headers := .Headers -}} + + {{if (and $hasHeaders (not $isRef)) -}} + type {{$opid}}{{$statusCode}}ResponseHeaders struct { + {{range .Headers -}} + {{.GoName}} {{.Schema.TypeDecl}} + {{end -}} + } + {{end}} + + {{range .Contents}} + {{$receiverTypeName := printf "%s%s%s%s" $opid $statusCode .NameTagOrContentType "Response"}} + {{if eq .NameTag "Text" -}} + type {{$receiverTypeName}} string + {{else if and $fixedStatusCode $isRef -}} + {{ if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) (eq .NameTag "Multipart") -}} + type {{$receiverTypeName}} {{$ref}}{{.NameTagOrContentType}}Response + {{else if $isExternalRef -}} + type {{$receiverTypeName}} struct { {{$ref}} } + {{else -}} + type {{$receiverTypeName}} struct{ {{$ref}}{{.NameTagOrContentType}}Response } + {{end}} + {{else if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) -}} + type {{$receiverTypeName}} {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{if and .Schema.IsRef (not .Schema.IsExternalRef)}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}} + {{else -}} + type {{$receiverTypeName}} struct { + Body {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{.Schema.TypeDecl}}{{else}}io.Reader{{end}} + {{if $hasHeaders -}} + Headers {{if $isRef}}{{$ref}}{{else}}{{$opid}}{{$statusCode}}{{end}}ResponseHeaders + {{end -}} + + {{if not $fixedStatusCode -}} + StatusCode int + {{end -}} + + {{if not .HasFixedContentType -}} + ContentType string + {{end -}} + + {{if not .IsSupported -}} + ContentLength int64 + {{end -}} + } + {{end}} + + func (response {{$receiverTypeName}}) Visit{{$opid}}Response(ctx iris.Context) error { + {{range $headers -}} + ctx.ResponseWriter().Header().Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}})) + {{end -}} + {{if eq .NameTag "Multipart" -}} + writer := multipart.NewWriter(ctx.ResponseWriter()) + {{end -}} + ctx.ResponseWriter().Header().Set("Content-Type", {{if eq .NameTag "Multipart"}}{{if eq .ContentType "multipart/form-data"}}writer.FormDataContentType(){{else}}mime.FormatMediaType("{{.ContentType}}", map[string]string{"boundary": writer.Boundary()}){{end}}{{else if .HasFixedContentType }}{{.ContentType | toGoString}}{{else}}response.ContentType{{end}}) + {{if not .IsSupported -}} + if response.ContentLength != 0 { + ctx.ResponseWriter().Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + {{end -}} + ctx.StatusCode({{if $fixedStatusCode}}{{$statusCode}}{{else}}response.StatusCode{{end}}) + {{$hasBodyVar := or ($hasHeaders) (not $fixedStatusCode) (not .IsSupported)}} + {{if .IsJSON -}} + {{$hasUnionElements := ne 0 (len .Schema.UnionElements)}} + return ctx.JSON(&{{if $hasBodyVar}}response.Body{{else}}response{{end}}{{if $hasUnionElements}}.union{{end}}) + {{else if eq .NameTag "Text" -}} + _, err := ctx.WriteString(string({{if $hasBodyVar}}response.Body{{else}}response{{end}})) + return err + {{else if eq .NameTag "Formdata" -}} + if form, err := runtime.MarshalForm({{if $hasBodyVar}}response.Body{{else}}response{{end}}, nil); err != nil { + return err + } else { + _, err := ctx.WriteString(form.Encode()) + return err + } + {{else if eq .NameTag "Multipart" -}} + defer writer.Close() + return {{if $hasBodyVar}}response.Body{{else}}response{{end}}(writer); + {{else -}} + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(ctx.ResponseWriter(), response.Body) + return err + {{end}}{{/* if eq .NameTag "JSON" */ -}} + } + {{end}} + + {{if eq 0 (len .Contents) -}} + {{if and $fixedStatusCode $isRef -}} + type {{$opid}}{{$statusCode}}Response {{if not $isExternalRef}}={{end}} {{$ref}}Response + {{else -}} + type {{$opid}}{{$statusCode}}Response struct { + {{if $hasHeaders -}} + Headers {{if $isRef}}{{$ref}}{{else}}{{$opid}}{{$statusCode}}{{end}}ResponseHeaders + {{end}} + {{if not $fixedStatusCode -}} + StatusCode int + {{end -}} + } + {{end -}} + func (response {{$opid}}{{$statusCode}}Response) Visit{{$opid}}Response(ctx iris.Context) error { + {{range $headers -}} + ctx.ResponseWriter().Header().Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}})) + {{end -}} + ctx.StatusCode({{if $fixedStatusCode}}{{$statusCode}}{{else}}response.StatusCode{{end}}) + return nil + } + {{end}} + {{end}} +{{end}} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { +{{range .}}{{.SummaryAsComment }} +// ({{.Method}} {{.Path}}) +{{$opid := .OperationId -}} +{{$opid}}(ctx context.Context, request {{$opid | ucFirst}}RequestObject) ({{$opid | ucFirst}}ResponseObject, error) +{{end}}{{/* range . */ -}} +} diff --git a/pkg/codegen/templates/strict/strict-iris.tmpl b/pkg/codegen/templates/strict/strict-iris.tmpl new file mode 100644 index 0000000000..6ed301574c --- /dev/null +++ b/pkg/codegen/templates/strict/strict-iris.tmpl @@ -0,0 +1,121 @@ +type StrictHandlerFunc = strictiris.StrictIrisHandlerFunc +type StrictMiddlewareFunc = strictiris.StrictIrisMiddlewareFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +{{range .}} + {{$opid := .OperationId}} + // {{$opid}} operation middleware + func (sh *strictHandler) {{.OperationId}}(ctx iris.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) { + var request {{$opid | ucFirst}}RequestObject + + {{range .PathParams -}} + request.{{.GoName}} = {{.GoVariableName}} + {{end -}} + + {{if .RequiresParamObject -}} + request.Params = params + {{end -}} + + {{ if .HasMaskedRequestContentTypes -}} + request.ContentType = ctx.GetContentTypeRequested() + {{end -}} + + {{$multipleBodies := gt (len .Bodies) 1 -}} + {{range .Bodies -}} + {{if $multipleBodies}}if strings.HasPrefix(ctx.GetHeader("Content-Type"), "{{.ContentType}}") { {{end}} + {{if .IsJSON }} + var body {{$opid}}{{.NameTag}}RequestBody + if err := ctx.ReadJSON(&body); err != nil { + {{if not .Required -}} + if !errors.Is(err, io.EOF) { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + {{else -}} + ctx.StopWithError(http.StatusBadRequest, err) + return + {{end -}} + } {{if not .Required -}} else { {{end}} + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{if not .Required -}} } {{end}} + {{else if eq .NameTag "Formdata" -}} + if err := ctx.Request().ParseForm(); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + var body {{$opid}}{{.NameTag}}RequestBody + if err := runtime.BindForm(&body, ctx.Request().Form, nil, nil); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else if eq .NameTag "Multipart" -}} + {{if eq .ContentType "multipart/form-data" -}} + if reader, err := ctx.Request().MultipartReader(); err == nil { + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = reader + } else { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + {{else -}} + if _, params, err := mime.ParseMediaType(ctx.Request().Header.Get("Content-Type")); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } else if boundary := params["boundary"]; boundary == "" { + ctx.StopWithError(http.StatusBadRequest, http.ErrMissingBoundary) + return + } else { + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = multipart.NewReader(ctx.Request().Body, boundary) + } + {{end -}} + {{else if eq .NameTag "Text" -}} + data, err := io.ReadAll(ctx.Request().Body) + if err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + {{if not .Required -}} + if len(data) > 0 { + {{end -}} + body := {{$opid}}{{.NameTag}}RequestBody(data) + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{if not .Required -}} + } + {{end -}} + {{else -}} + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = ctx.Request().Body + {{end}}{{/* if eq .NameTag "JSON" */ -}} + {{if $multipleBodies}}}{{end}} + {{end}}{{/* range .Bodies */}} + + handler := func(ctx iris.Context, request interface{}) (interface{}, error) { + return sh.ssi.{{.OperationId}}(ctx, request.({{$opid | ucFirst}}RequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "{{.OperationId}}") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } else if validResponse, ok := response.({{$opid | ucFirst}}ResponseObject); ok { + if err := validResponse.Visit{{$opid}}Response(ctx); err != nil { + ctx.StopWithError(http.StatusBadRequest, err) + return + } + } else if response != nil { + ctx.Writef("Unexpected response type: %T", response) + return + } + } +{{end}} diff --git a/pkg/codegen/templates/union-and-additional-properties.tmpl b/pkg/codegen/templates/union-and-additional-properties.tmpl index 79b4c67b2e..6ec69f5e85 100644 --- a/pkg/codegen/templates/union-and-additional-properties.tmpl +++ b/pkg/codegen/templates/union-and-additional-properties.tmpl @@ -54,12 +54,12 @@ func (a {{.TypeName}}) MarshalJSON() ([]byte, error) { } } {{range .Schema.Properties}} -{{if not .Required}}if a.{{.GoFieldName}} != nil { {{end}} +{{if .RequiresNilCheck}}if a.{{.GoFieldName}} != nil { {{end}} object["{{.JsonFieldName}}"], err = json.Marshal(a.{{.GoFieldName}}) if err != nil { return nil, fmt.Errorf("error marshaling '{{.JsonFieldName}}': %w", err) } -{{if not .Required}} }{{end}} +{{if .RequiresNilCheck}} }{{end}} {{end}} for fieldName, field := range a.AdditionalProperties { object[fieldName], err = json.Marshal(field) diff --git a/pkg/codegen/templates/union.tmpl b/pkg/codegen/templates/union.tmpl index 06c91f4f13..61c2bf37fd 100644 --- a/pkg/codegen/templates/union.tmpl +++ b/pkg/codegen/templates/union.tmpl @@ -2,6 +2,7 @@ {{$typeName := .TypeName -}} {{$discriminator := .Schema.Discriminator}} {{$properties := .Schema.Properties -}} + {{$numberOfUnionTypes := len .Schema.UnionElements -}} {{range .Schema.UnionElements}} {{$element := . -}} // As{{ .Method }} returns the union data inside the {{$typeName}} as a {{.}} @@ -14,16 +15,18 @@ // From{{ .Method }} overwrites any union data inside the {{$typeName}} as the provided {{.}} func (t *{{$typeName}}) From{{ .Method }} (v {{.}}) error { {{if $discriminator -}} - {{range $value, $type := $discriminator.Mapping -}} - {{if eq $type $element -}} - {{$hasProperty := false -}} - {{range $properties -}} - {{if eq .GoFieldName $discriminator.PropertyName -}} - t.{{$discriminator.PropertyName}} = "{{$value}}" - {{$hasProperty = true -}} + {{if eq $numberOfUnionTypes (len $discriminator.Mapping) -}} + {{range $value, $type := $discriminator.Mapping -}} + {{if eq $type $element -}} + {{$hasProperty := false -}} + {{range $properties -}} + {{if eq .GoFieldName $discriminator.PropertyName -}} + t.{{$discriminator.PropertyName}} = "{{$value}}" + {{$hasProperty = true -}} + {{end -}} {{end -}} + {{if not $hasProperty}}v.{{$discriminator.PropertyName}} = "{{$value}}"{{end}} {{end -}} - {{if not $hasProperty}}v.{{$discriminator.PropertyName}} = "{{$value}}"{{end}} {{end -}} {{end -}} {{end -}} @@ -35,16 +38,18 @@ // Merge{{ .Method }} performs a merge with any union data inside the {{$typeName}}, using the provided {{.}} func (t *{{$typeName}}) Merge{{ .Method }} (v {{.}}) error { {{if $discriminator -}} - {{range $value, $type := $discriminator.Mapping -}} - {{if eq $type $element -}} - {{$hasProperty := false -}} - {{range $properties -}} - {{if eq .GoFieldName $discriminator.PropertyName -}} - t.{{$discriminator.PropertyName}} = "{{$value}}" - {{$hasProperty = true -}} + {{if eq $numberOfUnionTypes (len $discriminator.Mapping) -}} + {{range $value, $type := $discriminator.Mapping -}} + {{if eq $type $element -}} + {{$hasProperty := false -}} + {{range $properties -}} + {{if eq .GoFieldName $discriminator.PropertyName -}} + t.{{$discriminator.PropertyName}} = "{{$value}}" + {{$hasProperty = true -}} + {{end -}} {{end -}} + {{if not $hasProperty}}v.{{$discriminator.PropertyName}} = "{{$value}}"{{end}} {{end -}} - {{if not $hasProperty}}v.{{$discriminator.PropertyName}} = "{{$value}}"{{end}} {{end -}} {{end -}} {{end -}} @@ -53,7 +58,7 @@ return err } - merged, err := runtime.JsonMerge(b, t.union) + merged, err := runtime.JSONMerge(t.union, b) t.union = merged return err } @@ -102,12 +107,12 @@ } } {{range .Schema.Properties}} - {{if not .Required}}if t.{{.GoFieldName}} != nil { {{end}} + {{if .RequiresNilCheck}}if t.{{.GoFieldName}} != nil { {{end}} object["{{.JsonFieldName}}"], err = json.Marshal(t.{{.GoFieldName}}) if err != nil { return nil, fmt.Errorf("error marshaling '{{.JsonFieldName}}': %w", err) } - {{if not .Required}} }{{end}} + {{if .RequiresNilCheck}} }{{end}} {{end -}} b, err = json.Marshal(object) {{end -}} diff --git a/pkg/codegen/test_schema.json b/pkg/codegen/test_schema.json new file mode 100644 index 0000000000..d64939498e --- /dev/null +++ b/pkg/codegen/test_schema.json @@ -0,0 +1,15 @@ +{ + "title": "node", + "type": "object", + "description": "Represents a node", + "properties": { + "id": { + "type": "string", + "format": "uri-reference" + }, + "type": { + "type": "string", + "pattern": "^[A-Z][a-zA-Z0-9]*$" + } + } +} \ No newline at end of file diff --git a/pkg/codegen/test_spec.yaml b/pkg/codegen/test_spec.yaml index 7491c69c31..f20cbecdca 100644 --- a/pkg/codegen/test_spec.yaml +++ b/pkg/codegen/test_spec.yaml @@ -108,6 +108,25 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + /user: + get: + tags: + - mergeAllOf + summary: Merges allOf ref-ing a JSON schema + operationId: getUser + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/User' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' components: schemas: @@ -145,15 +164,45 @@ components: format: date-time CatDead: + required: + - sliced_birds + - very_dead_since + - very_memorable_birds properties: name: type: string + favourite_birds: + type: array + items: + type: string + nullable: true + detested_birds: + type: array + items: + type: string + sliced_birds: + type: array + items: + type: string + forgettable_birds: + additionalProperties: + type: string + nullable: true + memorable_birds: + additionalProperties: + type: string + very_memorable_birds: + additionalProperties: + type: string dead_since: type: string format: date-time x-oapi-codegen-extra-tags: tag1: value1 tag2: value2 + very_dead_since: + type: string + format: date-time cause: type: string enum: [ car, dog, oldage ] @@ -177,3 +226,12 @@ components: - na - single - double + User: + allOf: + - $ref: ./test_schema.json + - type: object + additionalProperties: false + properties: + name: + type: string + description: User name diff --git a/pkg/codegen/test_specs/remote-external-reference.yaml b/pkg/codegen/test_specs/remote-external-reference.yaml index b966df199f..1b425cd134 100644 --- a/pkg/codegen/test_specs/remote-external-reference.yaml +++ b/pkg/codegen/test_specs/remote-external-reference.yaml @@ -21,6 +21,6 @@ components: item: type: object oneOf: - - $ref: 'https://raw.githubusercontent.com/deepmap/oapi-codegen/master/examples/petstore-expanded/petstore-expanded.yaml#/components/schemas/NewPet' + - $ref: 'https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/master/examples/petstore-expanded/petstore-expanded.yaml#/components/schemas/NewPet' required: - item diff --git a/pkg/codegen/test_specs/x-go-type-skip-optional-pointer.yaml b/pkg/codegen/test_specs/x-go-type-skip-optional-pointer.yaml new file mode 100644 index 0000000000..404eaca78d --- /dev/null +++ b/pkg/codegen/test_specs/x-go-type-skip-optional-pointer.yaml @@ -0,0 +1,54 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Example with x-go-type-skip-optional-pointer +paths: + /check: + get: + summary: Return example + responses: + '200': + description: Ok + content: + application/json: + schema: + required: + - requiredField + properties: + # Optional field where the type is a pointer to a string, and + # the x-go-type-skip-optional-pointer is not set. + optionalField: + type: string + # Optional field where the type is a pointer to a string, and + # the x-go-type-skip-optional-pointer is set to false. + optionalFieldSkipFalse: + type: string + x-go-type-skip-optional-pointer: false + # Optional field where the type is a pointer to a string, and + # the x-go-type-skip-optional-pointer is set to true. + optionalFieldSkipTrue: + type: string + x-go-type-skip-optional-pointer: true + # Optional field where the type is a pointer to a string, and + # the x-go-type-skip-optional-pointer is set to true. + customTypeWithSkipTrue: + type: string + x-go-type: string + x-go-type-skip-optional-pointer: true + # Nullable field where the type is a pointer to a string, and + # the x-go-type-skip-optional-pointer is set to false. + nullableFieldSkipFalse: + type: string + nullable: true + x-go-type-skip-optional-pointer: false + # Nullable field where the type is a pointer to a string, and + # the x-go-type-skip-optional-pointer is set to true. + nullableFieldSkipTrue: + type: string + nullable: true + x-go-type-skip-optional-pointer: true + # Check that x-go-type-skip-optional-pointer has no effect on + # required fields. + requiredField: + type: string + x-go-type-skip-optional-pointer: false diff --git a/pkg/codegen/typemapping.go b/pkg/codegen/typemapping.go new file mode 100644 index 0000000000..f5e20b54ac --- /dev/null +++ b/pkg/codegen/typemapping.go @@ -0,0 +1,109 @@ +package codegen + +// SimpleTypeSpec defines the Go type for an OpenAPI type/format combination, +// along with any import required to use it. +type SimpleTypeSpec struct { + Type string `yaml:"type" json:"type"` + Import string `yaml:"import,omitempty" json:"import,omitempty"` +} + +// FormatMapping defines the default Go type and format-specific overrides +// for an OpenAPI type. +type FormatMapping struct { + Default SimpleTypeSpec `yaml:"default" json:"default"` + Formats map[string]SimpleTypeSpec `yaml:"formats,omitempty" json:"formats,omitempty"` +} + +// TypeMapping defines the mapping from OpenAPI types to Go types. +type TypeMapping struct { + Integer FormatMapping `yaml:"integer,omitempty" json:"integer,omitempty"` + Number FormatMapping `yaml:"number,omitempty" json:"number,omitempty"` + Boolean FormatMapping `yaml:"boolean,omitempty" json:"boolean,omitempty"` + String FormatMapping `yaml:"string,omitempty" json:"string,omitempty"` +} + +// Merge returns a new TypeMapping with user overrides applied on top of base. +func (base TypeMapping) Merge(user TypeMapping) TypeMapping { + return TypeMapping{ + Integer: base.Integer.merge(user.Integer), + Number: base.Number.merge(user.Number), + Boolean: base.Boolean.merge(user.Boolean), + String: base.String.merge(user.String), + } +} + +func (base FormatMapping) merge(user FormatMapping) FormatMapping { + result := FormatMapping{ + Default: base.Default, + Formats: make(map[string]SimpleTypeSpec), + } + + // Copy base formats + for k, v := range base.Formats { + result.Formats[k] = v + } + + // Override with user default if specified + if user.Default.Type != "" { + result.Default = user.Default + } + + // Override/add user formats + for k, v := range user.Formats { + result.Formats[k] = v + } + + return result +} + +// Resolve returns the SimpleTypeSpec for a given format string. +// If the format has a specific mapping, that is returned; otherwise the default is used. +func (fm FormatMapping) Resolve(format string) SimpleTypeSpec { + if format != "" { + if spec, ok := fm.Formats[format]; ok { + return spec + } + } + return fm.Default +} + +// DefaultTypeMapping provides the default OpenAPI type/format to Go type mappings. +var DefaultTypeMapping = TypeMapping{ + Integer: FormatMapping{ + Default: SimpleTypeSpec{Type: "int"}, + Formats: map[string]SimpleTypeSpec{ + "int": {Type: "int"}, + "int8": {Type: "int8"}, + "int16": {Type: "int16"}, + "int32": {Type: "int32"}, + "int64": {Type: "int64"}, + "uint": {Type: "uint"}, + "uint8": {Type: "uint8"}, + "uint16": {Type: "uint16"}, + "uint32": {Type: "uint32"}, + "uint64": {Type: "uint64"}, + }, + }, + Number: FormatMapping{ + Default: SimpleTypeSpec{Type: "float32"}, + Formats: map[string]SimpleTypeSpec{ + "float": {Type: "float32"}, + "double": {Type: "float64"}, + }, + }, + Boolean: FormatMapping{ + Default: SimpleTypeSpec{Type: "bool"}, + }, + String: FormatMapping{ + Default: SimpleTypeSpec{Type: "string"}, + Formats: map[string]SimpleTypeSpec{ + "byte": {Type: "[]byte"}, + "email": {Type: "openapi_types.Email"}, + "date": {Type: "openapi_types.Date"}, + "date-time": {Type: "time.Time", Import: "time"}, + "json": {Type: "json.RawMessage", Import: "encoding/json"}, + "uuid": {Type: "openapi_types.UUID"}, + "binary": {Type: "openapi_types.File"}, + }, + }, +} diff --git a/pkg/codegen/typemapping_test.go b/pkg/codegen/typemapping_test.go new file mode 100644 index 0000000000..ca593cfd5d --- /dev/null +++ b/pkg/codegen/typemapping_test.go @@ -0,0 +1,88 @@ +package codegen + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFormatMapping_Resolve(t *testing.T) { + fm := FormatMapping{ + Default: SimpleTypeSpec{Type: "int"}, + Formats: map[string]SimpleTypeSpec{ + "int32": {Type: "int32"}, + "int64": {Type: "int64"}, + }, + } + + assert.Equal(t, "int", fm.Resolve("").Type) + assert.Equal(t, "int32", fm.Resolve("int32").Type) + assert.Equal(t, "int64", fm.Resolve("int64").Type) + assert.Equal(t, "int", fm.Resolve("unknown-format").Type) +} + +func TestTypeMapping_Merge(t *testing.T) { + base := DefaultTypeMapping + + user := TypeMapping{ + Integer: FormatMapping{ + Default: SimpleTypeSpec{Type: "int64"}, + }, + String: FormatMapping{ + Formats: map[string]SimpleTypeSpec{ + "date-time": {Type: "civil.DateTime", Import: "cloud.google.com/go/civil"}, + }, + }, + } + + merged := base.Merge(user) + + // Integer default overridden + assert.Equal(t, "int64", merged.Integer.Default.Type) + // Integer formats still inherited from base + assert.Equal(t, "int32", merged.Integer.Formats["int32"].Type) + + // String date-time overridden + assert.Equal(t, "civil.DateTime", merged.String.Formats["date-time"].Type) + assert.Equal(t, "cloud.google.com/go/civil", merged.String.Formats["date-time"].Import) + // String default still inherited from base + assert.Equal(t, "string", merged.String.Default.Type) + // Other string formats still inherited + assert.Equal(t, "openapi_types.UUID", merged.String.Formats["uuid"].Type) + + // Number and Boolean unchanged + assert.Equal(t, "float32", merged.Number.Default.Type) + assert.Equal(t, "bool", merged.Boolean.Default.Type) +} + +func TestDefaultTypeMapping_Completeness(t *testing.T) { + // Verify all the default mappings match what was previously hardcoded + dm := DefaultTypeMapping + + // Integer + assert.Equal(t, "int", dm.Integer.Resolve("").Type) + assert.Equal(t, "int32", dm.Integer.Resolve("int32").Type) + assert.Equal(t, "int64", dm.Integer.Resolve("int64").Type) + assert.Equal(t, "uint32", dm.Integer.Resolve("uint32").Type) + assert.Equal(t, "int", dm.Integer.Resolve("unknown").Type) + + // Number + assert.Equal(t, "float32", dm.Number.Resolve("").Type) + assert.Equal(t, "float32", dm.Number.Resolve("float").Type) + assert.Equal(t, "float64", dm.Number.Resolve("double").Type) + assert.Equal(t, "float32", dm.Number.Resolve("unknown").Type) + + // Boolean + assert.Equal(t, "bool", dm.Boolean.Resolve("").Type) + + // String + assert.Equal(t, "string", dm.String.Resolve("").Type) + assert.Equal(t, "[]byte", dm.String.Resolve("byte").Type) + assert.Equal(t, "openapi_types.Email", dm.String.Resolve("email").Type) + assert.Equal(t, "openapi_types.Date", dm.String.Resolve("date").Type) + assert.Equal(t, "time.Time", dm.String.Resolve("date-time").Type) + assert.Equal(t, "json.RawMessage", dm.String.Resolve("json").Type) + assert.Equal(t, "openapi_types.UUID", dm.String.Resolve("uuid").Type) + assert.Equal(t, "openapi_types.File", dm.String.Resolve("binary").Type) + assert.Equal(t, "string", dm.String.Resolve("unknown").Type) +} diff --git a/pkg/codegen/utils.go b/pkg/codegen/utils.go index c5b0e60deb..adfeb7c89f 100644 --- a/pkg/codegen/utils.go +++ b/pkg/codegen/utils.go @@ -14,10 +14,13 @@ package codegen import ( + "bytes" "fmt" "go/token" "net/url" + "reflect" "regexp" + "slices" "sort" "strconv" "strings" @@ -30,8 +33,70 @@ var ( pathParamRE *regexp.Regexp predeclaredSet map[string]struct{} separatorSet map[rune]struct{} + nameNormalizer NameNormalizer = ToCamelCase ) +type NameNormalizerFunction string + +const ( + // NameNormalizerFunctionUnset is the default case, where the `name-normalizer` option hasn't been set. This will use the `ToCamelCase` function. + // + // See the docs for `NameNormalizerFunctionToCamelCase` for more details. + NameNormalizerFunctionUnset NameNormalizerFunction = "" + // NameNormalizerFunctionToCamelCase will use the `ToCamelCase` function. + // + // For instance: + // + // - `getHttpPet` => `GetHttpPet` + // - `OneOf2things` => `OneOf2things` + NameNormalizerFunctionToCamelCase NameNormalizerFunction = "ToCamelCase" + // NameNormalizerFunctionToCamelCaseWithDigits will use the `NameNormalizerFunctionToCamelCaseWithDigits` function. + // + // For instance: + // + // - `getHttpPet` => `GetHttpPet` + // - `OneOf2things` => `OneOf2Things` + NameNormalizerFunctionToCamelCaseWithDigits NameNormalizerFunction = "ToCamelCaseWithDigits" + // NameNormalizerFunctionToCamelCaseWithInitialisms will use the `NameNormalizerFunctionToCamelCaseWithInitialisms` function. + // + // For instance: + // + // - `getHttpPet` => `GetHTTPPet` + // - `OneOf2things` => `OneOf2things` + NameNormalizerFunctionToCamelCaseWithInitialisms NameNormalizerFunction = "ToCamelCaseWithInitialisms" +) + +// NameNormalizer is a function that takes a type name, and returns that type name converted into a different format. +// +// This may be an Operation ID i.e. `retrieveUserRequests` or a Schema name i.e. `BigBlockOfCheese` +// +// NOTE: this must return a string that can be used as a valid Go type name +type NameNormalizer func(string) string + +type NameNormalizerMap map[NameNormalizerFunction]NameNormalizer + +func (m NameNormalizerMap) Options() []string { + options := make([]string, 0, len(m)) + + for key := range NameNormalizers { + options = append(options, string(key)) + } + + sort.Strings(options) + + return options +} + +// NameNormalizers contains the valid options for `NameNormalizerFunction`s that `oapi-codegen` supports. +// +// If you are calling `oapi-codegen` as a library, this allows you to specify your own normalisation types before generating code. +var NameNormalizers = NameNormalizerMap{ + NameNormalizerFunctionUnset: ToCamelCase, + NameNormalizerFunctionToCamelCase: ToCamelCase, + NameNormalizerFunctionToCamelCaseWithDigits: ToCamelCaseWithDigits, + NameNormalizerFunctionToCamelCaseWithInitialisms: ToCamelCaseWithInitialisms, +} + func init() { pathParamRE = regexp.MustCompile(`{[.;?]?([^{}*]+)\*?}`) @@ -132,6 +197,27 @@ func LowercaseFirstCharacter(str string) string { return string(runes) } +// Lowercase the first upper characters in a string for case of abbreviation. +// This assumes UTF-8, so we have to be careful with unicode, don't treat it as a byte array. +func LowercaseFirstCharacters(str string) string { + if str == "" { + return "" + } + + runes := []rune(str) + + for i := range runes { + next := i + 1 + if i != 0 && next < len(runes) && unicode.IsLower(runes[next]) { + break + } + + runes[i] = unicode.ToLower(runes[i]) + } + + return string(runes) +} + // ToCamelCase will convert query-arg style strings to CamelCase. We will // use `., -, +, :, ;, _, ~, ' ', (, ), {, }, [, ]` as valid delimiters for words. // So, "word.word-word+word:word;word_word~word word(word)word{word}[word]" @@ -139,155 +225,207 @@ func LowercaseFirstCharacter(str string) string { func ToCamelCase(str string) string { s := strings.Trim(str, " ") - n := "" + var n strings.Builder capNext := true for _, v := range s { if unicode.IsUpper(v) { - n += string(v) + n.WriteString(string(v)) } if unicode.IsDigit(v) { - n += string(v) + n.WriteString(string(v)) } if unicode.IsLower(v) { if capNext { - n += strings.ToUpper(string(v)) + n.WriteString(strings.ToUpper(string(v))) } else { - n += string(v) + n.WriteString(string(v)) } } _, capNext = separatorSet[v] } - return n + return n.String() } -// SortedSchemaKeys returns the keys of the given SchemaRef dictionary in sorted -// order, since Golang scrambles dictionary keys -func SortedSchemaKeys(dict map[string]*openapi3.SchemaRef) []string { - keys := make([]string, len(dict)) - i := 0 - for key := range dict { - keys[i] = key - i++ +// ToCamelCaseWithDigits function will convert query-arg style strings to CamelCase. We will +// use `., -, +, :, ;, _, ~, ' ', (, ), {, }, [, ]` as valid delimiters for words. +// The difference of ToCamelCase that letter after a number becomes capitalized. +// So, "word.word-word+word:word;word_word~word word(word)word{word}[word]3word" +// would be converted to WordWordWordWordWordWordWordWordWordWordWordWordWord3Word +func ToCamelCaseWithDigits(s string) string { + res := bytes.NewBuffer(nil) + capNext := true + for _, v := range s { + if unicode.IsUpper(v) { + res.WriteRune(v) + capNext = false + continue + } + if unicode.IsDigit(v) { + res.WriteRune(v) + capNext = true + continue + } + if unicode.IsLower(v) { + if capNext { + res.WriteRune(unicode.ToUpper(v)) + } else { + res.WriteRune(v) + } + capNext = false + continue + } + capNext = true } - sort.Strings(keys) - return keys + return res.String() } -// SortedPathsKeys is the same as above, except it sorts the keys for a Paths -// dictionary. -func SortedPathsKeys(dict openapi3.Paths) []string { - keys := make([]string, len(dict)) - i := 0 - for key := range dict { - keys[i] = key - i++ +// ToCamelCaseWithInitialisms function will convert query-arg style strings to CamelCase with initialisms in uppercase. +// So, httpOperationId would be converted to HTTPOperationID +func ToCamelCaseWithInitialisms(s string) string { + parts := camelCaseMatchParts.FindAllString(ToCamelCaseWithDigits(s), -1) + for i := range parts { + if v, ok := globalState.initialismsMap[strings.ToLower(parts[i])]; ok { + parts[i] = v + } } - sort.Strings(keys) - return keys + return strings.Join(parts, "") } -// SortedOperationsKeys returns Operation dictionary keys in sorted order -func SortedOperationsKeys(dict map[string]*openapi3.Operation) []string { - keys := make([]string, len(dict)) - i := 0 - for key := range dict { - keys[i] = key - i++ - } - sort.Strings(keys) - return keys +var camelCaseMatchParts = regexp.MustCompile(`[\p{Lu}\d]+([\p{Ll}\d]+|$)`) + +var initialismsList = []string{ + "ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", + "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", + "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS", } -// SortedResponsesKeys returns Responses dictionary keys in sorted order -func SortedResponsesKeys(dict openapi3.Responses) []string { - keys := make([]string, len(dict)) - i := 0 - for key := range dict { - keys[i] = key - i++ +// targetWordRegex is a regex that matches all initialisms. +var targetWordRegex *regexp.Regexp + +func makeInitialismsMap(additionalInitialisms []string) map[string]string { + l := append(initialismsList, additionalInitialisms...) + + m := make(map[string]string, len(l)) + for i := range l { + m[strings.ToLower(l[i])] = l[i] } - sort.Strings(keys) - return keys + + // Create a regex to match the initialisms + targetWordRegex = regexp.MustCompile(`(?i)(` + strings.Join(l, "|") + `)`) + + return m } -func SortedHeadersKeys(dict openapi3.Headers) []string { - keys := make([]string, len(dict)) - i := 0 - for key := range dict { - keys[i] = key - i++ - } - sort.Strings(keys) - return keys +func ToCamelCaseWithInitialism(str string) string { + return replaceInitialism(ToCamelCase(str)) } -// SortedContentKeys returns Content dictionary keys in sorted order -func SortedContentKeys(dict openapi3.Content) []string { - keys := make([]string, len(dict)) - i := 0 - for key := range dict { - keys[i] = key - i++ - } - sort.Strings(keys) - return keys +func replaceInitialism(s string) string { + // These strings do not apply CamelCase + // Do not do CamelCase when these characters match when the preceding character is lowercase + return targetWordRegex.ReplaceAllStringFunc(s, func(s string) string { + // If the preceding character is lowercase, do not do CamelCase + if unicode.IsLower(rune(s[0])) { + return s + } + return strings.ToUpper(s) + }) } -// SortedStringKeys returns string map keys in sorted order -func SortedStringKeys(dict map[string]string) []string { - keys := make([]string, len(dict)) - i := 0 - for key := range dict { - keys[i] = key - i++ - } - sort.Strings(keys) - return keys +// mediaTypeToCamelCase converts a media type to a PascalCase representation +func mediaTypeToCamelCase(s string) string { + // ToCamelCase doesn't - and won't - add `/` to the characters it'll allow word boundary + s = strings.Replace(s, "/", "_", 1) + // including a _ to make sure that these are treated as word boundaries by `ToCamelCase` + s = strings.Replace(s, "*", "Wildcard_", 1) + s = strings.Replace(s, "+", "Plus_", 1) + + return ToCamelCaseWithInitialism(s) } -// SortedParameterKeys returns sorted keys for a ParameterRef dict -func SortedParameterKeys(dict map[string]*openapi3.ParameterRef) []string { - keys := make([]string, len(dict)) - i := 0 - for key := range dict { - keys[i] = key - i++ +// SortedMapKeys takes a map with keys of type string and returns a slice of those +// keys sorted lexicographically. +func SortedMapKeys[T any](m map[string]T) []string { + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) } sort.Strings(keys) return keys } -func SortedRequestBodyKeys(dict map[string]*openapi3.RequestBodyRef) []string { +// SortedSchemaKeys returns the keys of the given SchemaRef dictionary in sorted +// order, since Golang scrambles dictionary keys. This isn't a generic key sort, because +// we support an extension to grant specific orders to schemas to help control output +// ordering. +func SortedSchemaKeys(dict map[string]*openapi3.SchemaRef) []string { keys := make([]string, len(dict)) + orders := make(map[string]int64, len(dict)) i := 0 - for key := range dict { - keys[i] = key + + for key, v := range dict { + keys[i], orders[key] = key, int64(len(dict)) i++ + + if order, ok := schemaXOrder(v); ok { + orders[key] = order + } } - sort.Strings(keys) + + sort.Slice(keys, func(i, j int) bool { + if i, j := orders[keys[i]], orders[keys[j]]; i != j { + return i < j + } + return keys[i] < keys[j] + }) return keys } -func SortedSecurityRequirementKeys(sr openapi3.SecurityRequirement) []string { - keys := make([]string, len(sr)) - i := 0 - for key := range sr { - keys[i] = key - i++ +func schemaXOrder(v *openapi3.SchemaRef) (int64, bool) { + if v == nil { + return 0, false } - sort.Strings(keys) - return keys + + // YAML parsing picks up the x-order as a float64 + if order, ok := v.Extensions[extOrder].(float64); ok { + return int64(order), true + } + + if v.Value == nil { + return 0, false + } + + // if v.Value is set, then this is actually a `$ref`, and we should check if there's an x-order set on that + + // YAML parsing picks up the x-order as a float64 + if order, ok := v.Value.Extensions[extOrder].(float64); ok { + return int64(order), true + } + + return 0, false } // StringInArray checks whether the specified string is present in an array // of strings func StringInArray(str string, array []string) bool { - for _, elt := range array { - if elt == str { - return true - } + return slices.Contains(array, str) +} + +// RefPathToObjName returns the name of referenced object without changes. +// +// #/components/schemas/Foo -> Foo +// #/components/parameters/Bar -> Bar +// #/components/responses/baz_baz -> baz_baz +// document.json#/Foo -> Foo +// http://deepmap.com/schemas/document.json#/objObj -> objObj +// +// Does not check refPath correctness. +func RefPathToObjName(refPath string) string { + parts := strings.Split(refPath, "/") + if len(parts) > 0 { + return parts[len(parts)-1] } - return false + return "" } // RefPathToGoType takes a $ref value and converts it to a Go typename. @@ -304,44 +442,73 @@ func RefPathToGoType(refPath string) (string, error) { // refPathToGoType returns the Go typename for refPath given its func refPathToGoType(refPath string, local bool) (string, error) { if refPath[0] == '#' { - pathParts := strings.Split(refPath, "/") - depth := len(pathParts) - if local { - if depth != 4 { - return "", fmt.Errorf("unexpected reference depth: %d for ref: %s local: %t", depth, refPath, local) - } - } else if depth != 4 && depth != 2 { - return "", fmt.Errorf("unexpected reference depth: %d for ref: %s local: %t", depth, refPath, local) - } - - // Schemas may have been renamed locally, so look up the actual name in - // the spec. - name, err := findSchemaNameByRefPath(refPath, globalState.spec) - if err != nil { - return "", fmt.Errorf("error finding ref: %s in spec: %v", refPath, err) - } - if name != "" { - return name, nil - } - // lastPart now stores the final element of the type path. This is what - // we use as the base for a type name. - lastPart := pathParts[len(pathParts)-1] - return SchemaNameToTypeName(lastPart), nil + return refPathToGoTypeSelf(refPath, local) } pathParts := strings.Split(refPath, "#") if len(pathParts) != 2 { return "", fmt.Errorf("unsupported reference: %s", refPath) } remoteComponent, flatComponent := pathParts[0], pathParts[1] - if goImport, ok := importMapping[remoteComponent]; !ok { + goPkg, ok := globalState.importMapping[remoteComponent] + + if !ok { return "", fmt.Errorf("unrecognized external reference '%s'; please provide the known import for this reference using option --import-mapping", remoteComponent) - } else { - goType, err := refPathToGoType("#"+flatComponent, false) - if err != nil { - return "", err + } + + if goPkg.Path == importMappingCurrentPackage { + return refPathToGoTypeSelf(fmt.Sprintf("#%s", pathParts[1]), local) + } + + return refPathToGoTypeRemote(flatComponent, goPkg) + +} + +func refPathToGoTypeSelf(refPath string, local bool) (string, error) { + pathParts := strings.Split(refPath, "/") + depth := len(pathParts) + if local { + if depth != 4 { + return "", fmt.Errorf("unexpected reference depth: %d for ref: %s local: %t", depth, refPath, local) + } + } else if depth != 4 && depth != 2 { + return "", fmt.Errorf("unexpected reference depth: %d for ref: %s local: %t", depth, refPath, local) + } + + // When multi-pass name resolution is active, the resolved name takes + // precedence over the spec-given name. For a $ref like + // #/components/schemas/Thing, we pass the section ("schemas") and + // name ("Thing") to resolvedNameForComponent, which looks up the + // final Go type name assigned by the collision resolver. + // Note: the resolver prioritizes component schemas — if a schema and + // a response both claim "Thing", the component schema keeps the original + // name and the response becomes "ThingResponse". + if depth == 4 && pathParts[0] == "#" && pathParts[1] == "components" { + if resolved := resolvedNameForComponent(pathParts[2], pathParts[3]); resolved != "" { + return resolved, nil } - return fmt.Sprintf("%s.%s", goImport.Name, goType), nil } + + // Schemas may have been renamed locally, so look up the actual name in + // the spec. + name, err := findSchemaNameByRefPath(refPath, globalState.spec) + if err != nil { + return "", fmt.Errorf("error finding ref: %s in spec: %v", refPath, err) + } + if name != "" { + return name, nil + } + // lastPart now stores the final element of the type path. This is what + // we use as the base for a type name. + lastPart := pathParts[len(pathParts)-1] + return SchemaNameToTypeName(lastPart), nil +} + +func refPathToGoTypeRemote(flatComponent string, goPkg goImport) (string, error) { + goType, err := refPathToGoType("#"+flatComponent, false) + if err != nil { + return "", err + } + return fmt.Sprintf("%s.%s", goPkg.Name, goType), nil } // IsGoTypeReference takes a $ref value and checks if it has link to go type. @@ -363,6 +530,21 @@ func IsWholeDocumentReference(ref string) bool { return ref != "" && !strings.ContainsAny(ref, "#") } +// SwaggerUriToIrisUri converts a OpenAPI style path URI with parameters to an +// Iris compatible path URI. We need to replace all of OpenAPI parameters with +// +// {param} +// {param*} +// {.param} +// {.param*} +// {;param} +// {;param*} +// {?param} +// {?param*} +func SwaggerUriToIrisUri(uri string) string { + return pathParamRE.ReplaceAllString(uri, ":$1") +} + // SwaggerUriToEchoUri converts a OpenAPI style path URI with parameters to an // Echo compatible path URI. We need to replace all of OpenAPI parameters with // ":param". Valid input parameters are: @@ -443,6 +625,28 @@ func SwaggerUriToGorillaUri(uri string) string { return pathParamRE.ReplaceAllString(uri, "{$1}") } +// SwaggerUriToStdHttpUri converts a swagger style path URI with parameters to a +// Chi compatible path URI. We need to replace all Swagger parameters with +// "{param}". Valid input parameters are: +// +// {param} +// {param*} +// {.param} +// {.param*} +// {;param} +// {;param*} +// {?param} +// {?param*} +func SwaggerUriToStdHttpUri(uri string) string { + // https://pkg.go.dev/net/http#hdr-Patterns-ServeMux + // The special wildcard {$} matches only the end of the URL. For example, the pattern "/{$}" matches only the path "/", whereas the pattern "/" matches every path. + if uri == "/" { + return "/{$}" + } + + return pathParamRE.ReplaceAllString(uri, "{$1}") +} + // OrderedParamsFromUri returns the argument names, in order, in a given URI string, so for // /path/{param1}/{.param2*}/{?param3}, it would return param1, param2, param3 func OrderedParamsFromUri(uri string) []string { @@ -460,15 +664,29 @@ func ReplacePathParamsWithStr(uri string) string { } // SortParamsByPath reorders the given parameter definitions to match those in the path URI. +// If a parameter appears more than once in the path (e.g. Keycloak's +// /clients/{client-uuid}/roles/{role-name}/composites/clients/{client-uuid}), +// duplicates are removed and only the first occurrence determines the order. func SortParamsByPath(path string, in []ParameterDefinition) ([]ParameterDefinition, error) { pathParams := OrderedParamsFromUri(path) + + // Deduplicate, preserving first-occurrence order. + seen := make(map[string]struct{}, len(pathParams)) + uniqueParams := make([]string, 0, len(pathParams)) + for _, name := range pathParams { + if _, exists := seen[name]; !exists { + seen[name] = struct{}{} + uniqueParams = append(uniqueParams, name) + } + } + n := len(in) - if len(pathParams) != n { + if len(uniqueParams) != n { return nil, fmt.Errorf("path '%s' has %d positional parameters, but spec has %d declared", - path, len(pathParams), n) + path, len(uniqueParams), n) } - out := make([]ParameterDefinition, len(in)) - for i, name := range pathParams { + out := make([]ParameterDefinition, n) + for i, name := range uniqueParams { p := ParameterDefinitions(in).FindByName(name) if p == nil { return nil, fmt.Errorf("path '%s' refers to parameter '%s', which doesn't exist in specification", @@ -485,7 +703,7 @@ func IsGoKeyword(str string) bool { } // IsPredeclaredGoIdentifier returns whether the given string -// is a predefined go indentifier. +// is a predefined go identifier. // // See https://golang.org/ref/spec#Predeclared_identifiers func IsPredeclaredGoIdentifier(str string) bool { @@ -608,6 +826,10 @@ func typeNamePrefix(name string) (prefix string) { prefix += "Tilde" case '=': prefix += "Equal" + case '>': + prefix += "GreaterThan" + case '<': + prefix += "LessThan" case '#': prefix += "Hash" case '.': @@ -618,6 +840,8 @@ func typeNamePrefix(name string) (prefix string) { prefix += "Caret" case '%': prefix += "Percent" + case '_': + prefix += "Underscore" default: // Prepend "N" to schemas starting with a number if prefix == "" && unicode.IsDigit(r) { @@ -635,7 +859,7 @@ func typeNamePrefix(name string) (prefix string) { // SchemaNameToTypeName converts a Schema name to a valid Go type name. It converts to camel case, and makes sure the name is // valid in Go func SchemaNameToTypeName(name string) string { - return typeNamePrefix(name) + ToCamelCase(name) + return typeNamePrefix(name) + nameNormalizer(name) } // According to the spec, additionalProperties may be true, false, or a @@ -659,11 +883,18 @@ func SchemaHasAdditionalProperties(schema *openapi3.Schema) bool { // type name. func PathToTypeName(path []string) string { for i, p := range path { - path[i] = ToCamelCase(p) + path[i] = nameNormalizer(p) } return strings.Join(path, "_") } +// StringToGoString takes an arbitrary string and converts it to a valid Go string literal, +// including the quotes. For instance, `foo "bar"` would be converted to `"foo \"bar\""` +func StringToGoString(in string) string { + esc := strings.ReplaceAll(in, "\"", "\\\"") + return fmt.Sprintf("\"%s\"", esc) +} + // StringToGoComment renders a possible multi-line string as a valid Go-Comment. // Each line is prefixed as a comment. func StringToGoComment(in string) string { @@ -677,14 +908,25 @@ func StringWithTypeNameToGoComment(in, typeName string) string { return stringToGoCommentWithPrefix(in, typeName) } +func DeprecationComment(reason string) string { + content := "Deprecated:" // The colon is required at the end even without reason + if reason != "" { + content += fmt.Sprintf(" %s", reason) + } else { + content += " this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set" + } + + return stringToGoCommentWithPrefix(content, "") +} + func stringToGoCommentWithPrefix(in, prefix string) string { if len(in) == 0 || len(strings.TrimSpace(in)) == 0 { // ignore empty comment return "" } // Normalize newlines from Windows/Mac to Linux - in = strings.Replace(in, "\r\n", "\n", -1) - in = strings.Replace(in, "\r", "\n", -1) + in = strings.ReplaceAll(in, "\r\n", "\n") + in = strings.ReplaceAll(in, "\r", "\n") // Add comment to each line var lines []string @@ -796,6 +1038,9 @@ func renameRequestBody(requestBodyName string, requestBodyRef *openapi3.RequestB // if the schema wasn't found, and it'll only work successfully for schemas // defined within the spec that we parsed. func findSchemaNameByRefPath(refPath string, spec *openapi3.T) (string, error) { + if spec.Components == nil { + return "", nil + } pathElements := strings.Split(refPath, "/") // All local references will have 4 path elements. if len(pathElements) != 4 { @@ -839,7 +1084,7 @@ func ParseGoImportExtension(v *openapi3.SchemaRef) (*goImport, error) { goTypeImportExt := v.Value.Extensions[extPropGoImport] - importI, ok := goTypeImportExt.(map[string]interface{}) + importI, ok := goTypeImportExt.(map[string]any) if !ok { return nil, fmt.Errorf("failed to convert type: %T", goTypeImportExt) } @@ -878,5 +1123,18 @@ func TypeDefinitionsEquivalent(t1, t2 TypeDefinition) bool { if t1.TypeName != t2.TypeName { return false } - return t1.Schema.OAPISchema == t2.Schema.OAPISchema + return reflect.DeepEqual(t1.Schema.OAPISchema, t2.Schema.OAPISchema) +} + +// isAdditionalPropertiesExplicitFalse determines whether an openapi3.Schema is explicitly defined as `additionalProperties: false` +func isAdditionalPropertiesExplicitFalse(s *openapi3.Schema) bool { + if s.AdditionalProperties.Has == nil { + return false + } + + return *s.AdditionalProperties.Has == false //nolint:staticcheck +} + +func sliceContains[E comparable](s []E, v E) bool { + return slices.Contains(s, v) } diff --git a/pkg/codegen/utils_test.go b/pkg/codegen/utils_test.go index 518e287f5e..3f9e0e8b76 100644 --- a/pkg/codegen/utils_test.go +++ b/pkg/codegen/utils_test.go @@ -18,128 +18,230 @@ import ( "github.com/getkin/kin-openapi/openapi3" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestStringOps(t *testing.T) { - // Test that each substitution works - assert.Equal(t, "WordWordWORDWordWordWordWordWordWordWordWordWordWord", ToCamelCase("word.word-WORD+Word_word~word(Word)Word{Word}Word[Word]Word:Word;"), "Camel case conversion failed") - - // Make sure numbers don't interact in a funny way. - assert.Equal(t, "Number1234", ToCamelCase("number-1234"), "Number Camelcasing not working.") -} - -func TestSortedSchemaKeys(t *testing.T) { - dict := map[string]*openapi3.SchemaRef{ - "f": nil, - "c": nil, - "b": nil, - "e": nil, - "d": nil, - "a": nil, +func TestToCamelCase(t *testing.T) { + tests := []struct { + str string + want string + }{{ + str: "", + want: "", + }, { + str: " foo_bar ", + want: "FooBar", + }, { + str: "hi hello-hey-hallo", + want: "HiHelloHeyHallo", + }, { + str: "foo#bar", + want: "FooBar", + }, { + str: "foo2bar", + want: "Foo2bar", + }, { + // Test that each substitution works + str: "word.word-WORD+Word_word~word(Word)Word{Word}Word[Word]Word:Word;", + want: "WordWordWORDWordWordWordWordWordWordWordWordWordWord", + }, { + // Make sure numbers don't interact in a funny way. + str: "number-1234", + want: "Number1234", + }, } - - expected := []string{"a", "b", "c", "d", "e", "f"} - - assert.EqualValues(t, expected, SortedSchemaKeys(dict), "Keys are not sorted properly") -} - -func TestSortedPathsKeys(t *testing.T) { - dict := openapi3.Paths{ - "f": nil, - "c": nil, - "b": nil, - "e": nil, - "d": nil, - "a": nil, + for i := range tests { + tt := tests[i] + t.Run(tt.str, func(t *testing.T) { + require.Equal(t, tt.want, ToCamelCase(tt.str)) + }) } - - expected := []string{"a", "b", "c", "d", "e", "f"} - - assert.EqualValues(t, expected, SortedPathsKeys(dict), "Keys are not sorted properly") } -func TestSortedOperationsKeys(t *testing.T) { - dict := map[string]*openapi3.Operation{ - "f": nil, - "c": nil, - "b": nil, - "e": nil, - "d": nil, - "a": nil, +func TestToCamelCaseWithDigits(t *testing.T) { + tests := []struct { + str string + want string + }{{ + str: "", + want: "", + }, { + str: " foo_bar ", + want: "FooBar", + }, { + str: "hi hello-hey-hallo", + want: "HiHelloHeyHallo", + }, { + str: "foo#bar", + want: "FooBar", + }, { + str: "foo2bar", + want: "Foo2Bar", + }, { + str: "пир2пир", + want: "Пир2Пир", + }, { + // Test that each substitution works + str: "word.word3word-WORD+Word_word~word(Word)Word{Word}Word[Word]Word:Word;", + want: "WordWord3WordWORDWordWordWordWordWordWordWordWordWordWord", + }, { + // Make sure numbers don't interact in a funny way. + str: "number-1234", + want: "Number1234", + }, + } + for i := range tests { + tt := tests[i] + t.Run(tt.str, func(t *testing.T) { + require.Equal(t, tt.want, ToCamelCaseWithDigits(tt.str)) + }) } - - expected := []string{"a", "b", "c", "d", "e", "f"} - - assert.EqualValues(t, expected, SortedOperationsKeys(dict), "Keys are not sorted properly") } -func TestSortedResponsesKeys(t *testing.T) { - dict := openapi3.Responses{ - "f": nil, - "c": nil, - "b": nil, - "e": nil, - "d": nil, - "a": nil, +func TestToCamelCaseWithInitialisms(t *testing.T) { + tests := []struct { + str string + want string + }{{ + str: "", + want: "", + }, { + str: "hello", + want: "Hello", + }, { + str: "DBError", + want: "DBError", + }, { + str: "httpOperationId", + want: "HTTPOperationID", + }, { + str: "OperationId", + want: "OperationID", + }, { + str: "peer2peer", + want: "Peer2Peer", + }, { + str: "makeUtf8", + want: "MakeUTF8", + }, { + str: "utf8Hello", + want: "UTF8Hello", + }, { + str: "myDBError", + want: "MyDBError", + }, { + str: " DbLayer ", + want: "DBLayer", + }, { + str: "FindPetById", + want: "FindPetByID", + }, { + str: "MyHttpUrl", + want: "MyHTTPURL", + }, { + str: "find_user_by_uuid", + want: "FindUserByUUID", + }, { + str: "HelloПриветWorldМир42", + want: "HelloПриветWorldМир42", + }, { + str: "пир2пир", + want: "Пир2Пир", + }} + for i := range tests { + tt := tests[i] + t.Run(tt.str, func(t *testing.T) { + require.Equal(t, tt.want, ToCamelCaseWithInitialisms(tt.str)) + }) } - - expected := []string{"a", "b", "c", "d", "e", "f"} - - assert.EqualValues(t, expected, SortedResponsesKeys(dict), "Keys are not sorted properly") } -func TestSortedContentKeys(t *testing.T) { - dict := openapi3.Content{ - "f": nil, - "c": nil, - "b": nil, - "e": nil, - "d": nil, - "a": nil, +func TestSortedSchemaKeysWithXOrder(t *testing.T) { + withOrder := func(i float64) *openapi3.SchemaRef { + return &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + Extensions: map[string]any{"x-order": i}, + }, + } + } + dict := map[string]*openapi3.SchemaRef{ + "first": withOrder(1), + "minusTenth": withOrder(-10), + "zero": withOrder(0), + "minusHundredth_2": withOrder(-100), + "minusHundredth_1": withOrder(-100), + "afterFirst": withOrder(2), + "last": withOrder(100), + "middleA": nil, + "middleB": nil, + "middleC": nil, } - expected := []string{"a", "b", "c", "d", "e", "f"} + expected := []string{"minusHundredth_1", "minusHundredth_2", "minusTenth", "zero", "first", "afterFirst", "middleA", "middleB", "middleC", "last"} - assert.EqualValues(t, expected, SortedContentKeys(dict), "Keys are not sorted properly") + assert.EqualValues(t, expected, SortedSchemaKeys(dict), "Keys are not sorted properly") } -func TestSortedParameterKeys(t *testing.T) { - dict := map[string]*openapi3.ParameterRef{ - "f": nil, - "c": nil, - "b": nil, - "e": nil, - "d": nil, - "a": nil, - } +func TestSortedSchemaKeysWithXOrderFromParsed(t *testing.T) { + rawSpec := `--- +components: + schemas: + AlwaysLast: + type: string + x-order: 100000 + DateInterval: + type: object + required: + - name + properties: + end: + type: string + format: date + x-order: 2 + start: + type: string + format: date + x-order: 1 + ` + + loader := openapi3.NewLoader() + spec, err := loader.LoadFromData([]byte(rawSpec)) + require.NoError(t, err) + require.NotNil(t, spec.Components) + require.NotNil(t, spec.Components.Schemas) + + t.Run("for the top-level schemas", func(t *testing.T) { + expected := []string{"DateInterval", "AlwaysLast"} + + actual := SortedSchemaKeys(spec.Components.Schemas) + + assert.EqualValues(t, expected, actual) + }) - expected := []string{"a", "b", "c", "d", "e", "f"} + t.Run("for DateInterval's keys", func(t *testing.T) { + schemas, found := spec.Components.Schemas["DateInterval"] + require.True(t, found, "did not find `#/components/schemas/DateInterval`") - assert.EqualValues(t, expected, SortedParameterKeys(dict), "Keys are not sorted properly") -} + expected := []string{"start", "end"} -func TestSortedRequestBodyKeys(t *testing.T) { - dict := map[string]*openapi3.RequestBodyRef{ - "f": nil, - "c": nil, - "b": nil, - "e": nil, - "d": nil, - "a": nil, - } + actual := SortedSchemaKeys(schemas.Value.Properties) - expected := []string{"a", "b", "c", "d", "e", "f"} + assert.EqualValues(t, expected, actual, "Keys are not sorted properly") + }) - assert.EqualValues(t, expected, SortedRequestBodyKeys(dict), "Keys are not sorted properly") } func TestRefPathToGoType(t *testing.T) { - old := importMapping - importMapping = constructImportMapping(map[string]string{ - "doc.json": "externalref0", - "http://deepmap.com/doc.json": "externalref1", - }) - defer func() { importMapping = old }() + old := globalState.importMapping + globalState.importMapping = constructImportMapping( + map[string]string{ + "doc.json": "externalref0", + "http://deepmap.com/doc.json": "externalref1", + // using the "current package" mapping + "dj-current-package.yml": "-", + }, + ) + defer func() { globalState.importMapping = old }() tests := []struct { name string @@ -161,6 +263,11 @@ func TestRefPathToGoType(t *testing.T) { path: "#/components/responses/wibble", goType: "Wibble", }, + { + name: "local-mapped-current-package", + path: "dj-current-package.yml#/components/schemas/Foo", + goType: "Foo", + }, { name: "remote-root", path: "doc.json#/foo", @@ -229,6 +336,23 @@ func TestIsGoTypeReference(t *testing.T) { assert.Equal(t, false, IsGoTypeReference("http://deepmap.com/doc.json")) } +func TestSwaggerUriToIrisUri(t *testing.T) { + assert.Equal(t, "/path", SwaggerUriToIrisUri("/path")) + assert.Equal(t, "/path/:arg", SwaggerUriToIrisUri("/path/{arg}")) + assert.Equal(t, "/path/:arg1/:arg2", SwaggerUriToIrisUri("/path/{arg1}/{arg2}")) + assert.Equal(t, "/path/:arg1/:arg2/foo", SwaggerUriToIrisUri("/path/{arg1}/{arg2}/foo")) + + // Make sure all the exploded and alternate formats match too + assert.Equal(t, "/path/:arg/foo", SwaggerUriToIrisUri("/path/{arg}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToIrisUri("/path/{arg*}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToIrisUri("/path/{.arg}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToIrisUri("/path/{.arg*}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToIrisUri("/path/{;arg}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToIrisUri("/path/{;arg*}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToIrisUri("/path/{?arg}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToIrisUri("/path/{?arg*}/foo")) +} + func TestSwaggerUriToEchoUri(t *testing.T) { assert.Equal(t, "/path", SwaggerUriToEchoUri("/path")) assert.Equal(t, "/path/:arg", SwaggerUriToEchoUri("/path/{arg}")) @@ -280,12 +404,111 @@ func TestSwaggerUriToGorillaUri(t *testing.T) { // TODO assert.Equal(t, "/path/{arg}/foo", SwaggerUriToGorillaUri("/path/{?arg*}/foo")) } +func TestSwaggerUriToFiberUri(t *testing.T) { + assert.Equal(t, "/path", SwaggerUriToFiberUri("/path")) + assert.Equal(t, "/path/:arg", SwaggerUriToFiberUri("/path/{arg}")) + assert.Equal(t, "/path/:arg1/:arg2", SwaggerUriToFiberUri("/path/{arg1}/{arg2}")) + assert.Equal(t, "/path/:arg1/:arg2/foo", SwaggerUriToFiberUri("/path/{arg1}/{arg2}/foo")) + + // Make sure all the exploded and alternate formats match too + assert.Equal(t, "/path/:arg/foo", SwaggerUriToFiberUri("/path/{arg}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToFiberUri("/path/{arg*}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToFiberUri("/path/{.arg}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToFiberUri("/path/{.arg*}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToFiberUri("/path/{;arg}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToFiberUri("/path/{;arg*}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToFiberUri("/path/{?arg}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToFiberUri("/path/{?arg*}/foo")) +} + +func TestSwaggerUriToChiUri(t *testing.T) { + assert.Equal(t, "/path", SwaggerUriToChiUri("/path")) + assert.Equal(t, "/path/{arg}", SwaggerUriToChiUri("/path/{arg}")) + assert.Equal(t, "/path/{arg1}/{arg2}", SwaggerUriToChiUri("/path/{arg1}/{arg2}")) + assert.Equal(t, "/path/{arg1}/{arg2}/foo", SwaggerUriToChiUri("/path/{arg1}/{arg2}/foo")) + + // Make sure all the exploded and alternate formats match too + assert.Equal(t, "/path/{arg}/foo", SwaggerUriToChiUri("/path/{arg}/foo")) + assert.Equal(t, "/path/{arg}/foo", SwaggerUriToChiUri("/path/{arg*}/foo")) + assert.Equal(t, "/path/{arg}/foo", SwaggerUriToChiUri("/path/{.arg}/foo")) + assert.Equal(t, "/path/{arg}/foo", SwaggerUriToChiUri("/path/{.arg*}/foo")) + assert.Equal(t, "/path/{arg}/foo", SwaggerUriToChiUri("/path/{;arg}/foo")) + assert.Equal(t, "/path/{arg}/foo", SwaggerUriToChiUri("/path/{;arg*}/foo")) + assert.Equal(t, "/path/{arg}/foo", SwaggerUriToChiUri("/path/{?arg}/foo")) + assert.Equal(t, "/path/{arg}/foo", SwaggerUriToChiUri("/path/{?arg*}/foo")) +} + +func TestSwaggerUriToStdHttpUriUri(t *testing.T) { + assert.Equal(t, "/{$}", SwaggerUriToStdHttpUri("/")) + assert.Equal(t, "/path", SwaggerUriToStdHttpUri("/path")) + assert.Equal(t, "/path/{arg}", SwaggerUriToStdHttpUri("/path/{arg}")) + assert.Equal(t, "/path/{arg1}/{arg2}", SwaggerUriToStdHttpUri("/path/{arg1}/{arg2}")) + assert.Equal(t, "/path/{arg1}/{arg2}/foo", SwaggerUriToStdHttpUri("/path/{arg1}/{arg2}/foo")) + + // Make sure all the exploded and alternate formats match too + assert.Equal(t, "/path/{arg}/foo", SwaggerUriToStdHttpUri("/path/{arg}/foo")) + assert.Equal(t, "/path/{arg}/foo", SwaggerUriToStdHttpUri("/path/{arg*}/foo")) + assert.Equal(t, "/path/{arg}/foo", SwaggerUriToStdHttpUri("/path/{.arg}/foo")) + assert.Equal(t, "/path/{arg}/foo", SwaggerUriToStdHttpUri("/path/{.arg*}/foo")) + assert.Equal(t, "/path/{arg}/foo", SwaggerUriToStdHttpUri("/path/{;arg}/foo")) + assert.Equal(t, "/path/{arg}/foo", SwaggerUriToStdHttpUri("/path/{;arg*}/foo")) + assert.Equal(t, "/path/{arg}/foo", SwaggerUriToStdHttpUri("/path/{?arg}/foo")) + assert.Equal(t, "/path/{arg}/foo", SwaggerUriToStdHttpUri("/path/{?arg*}/foo")) +} + func TestOrderedParamsFromUri(t *testing.T) { result := OrderedParamsFromUri("/path/{param1}/{.param2}/{;param3*}/foo") assert.EqualValues(t, []string{"param1", "param2", "param3"}, result) result = OrderedParamsFromUri("/path/foo") assert.EqualValues(t, []string{}, result) + + // A parameter can appear more than once in the URI (e.g. Keycloak API). + // OrderedParamsFromUri faithfully returns all occurrences. + result = OrderedParamsFromUri("/admin/realms/{realm}/clients/{client-uuid}/roles/{role-name}/composites/clients/{client-uuid}") + assert.EqualValues(t, []string{"realm", "client-uuid", "role-name", "client-uuid"}, result) +} + +func TestSortParamsByPath(t *testing.T) { + strSchema := &openapi3.Schema{Type: &openapi3.Types{"string"}} + + t.Run("reorders params to match path order", func(t *testing.T) { + params := []ParameterDefinition{ + {ParamName: "b", In: "path", Spec: &openapi3.Parameter{Name: "b", Schema: &openapi3.SchemaRef{Value: strSchema}}}, + {ParamName: "a", In: "path", Spec: &openapi3.Parameter{Name: "a", Schema: &openapi3.SchemaRef{Value: strSchema}}}, + } + sorted, err := SortParamsByPath("/foo/{a}/bar/{b}", params) + require.NoError(t, err) + require.Len(t, sorted, 2) + assert.Equal(t, "a", sorted[0].ParamName) + assert.Equal(t, "b", sorted[1].ParamName) + }) + + t.Run("errors on missing parameter", func(t *testing.T) { + params := []ParameterDefinition{ + {ParamName: "a", In: "path", Spec: &openapi3.Parameter{Name: "a", Schema: &openapi3.SchemaRef{Value: strSchema}}}, + } + _, err := SortParamsByPath("/foo/{a}/bar/{b}", params) + assert.Error(t, err) + }) + + t.Run("handles duplicate path parameters", func(t *testing.T) { + // This is the Keycloak-style path where {client-uuid} appears twice. + // The spec only declares 3 unique parameters. + params := []ParameterDefinition{ + {ParamName: "realm", In: "path", Spec: &openapi3.Parameter{Name: "realm", Schema: &openapi3.SchemaRef{Value: strSchema}}}, + {ParamName: "client-uuid", In: "path", Spec: &openapi3.Parameter{Name: "client-uuid", Schema: &openapi3.SchemaRef{Value: strSchema}}}, + {ParamName: "role-name", In: "path", Spec: &openapi3.Parameter{Name: "role-name", Schema: &openapi3.SchemaRef{Value: strSchema}}}, + } + path := "/admin/realms/{realm}/clients/{client-uuid}/roles/{role-name}/composites/clients/{client-uuid}" + sorted, err := SortParamsByPath(path, params) + require.NoError(t, err) + // Should return 3 unique params in first-occurrence order + require.Len(t, sorted, 3) + assert.Equal(t, "realm", sorted[0].ParamName) + assert.Equal(t, "client-uuid", sorted[1].ParamName) + assert.Equal(t, "role-name", sorted[2].ParamName) + }) } func TestReplacePathParamsWithStr(t *testing.T) { @@ -293,6 +516,36 @@ func TestReplacePathParamsWithStr(t *testing.T) { assert.EqualValues(t, "/path/%s/%s/%s/foo", result) } +func TestStringToGoStringValue(t *testing.T) { + testCases := []struct { + input string + expected string + message string + }{ + { + input: ``, + expected: `""`, + message: "blank string should be converted to empty Go string literal", + }, + { + input: `application/json`, + expected: `"application/json"`, + message: "typical string should be returned as-is", + }, + { + input: `application/json; foo="bar"`, + expected: `"application/json; foo=\"bar\""`, + message: "string with quotes should include escape characters", + }, + } + for _, testCase := range testCases { + t.Run(testCase.message, func(t *testing.T) { + result := StringToGoString(testCase.input) + assert.EqualValues(t, testCase.expected, result, testCase.message) + }) + } +} + func TestStringToGoComment(t *testing.T) { testCases := []struct { input string @@ -424,11 +677,123 @@ func TestSchemaNameToTypeName(t *testing.T) { "@timestamp,": "Timestamp", "&now": "AndNow", "~": "Tilde", - "_foo": "Foo", + "_foo": "UnderscoreFoo", "=3": "Equal3", "#Tag": "HashTag", ".com": "DotCom", + "_1": "Underscore1", + ">=": "GreaterThanEqual", + "<=": "LessThanEqual", + "<": "LessThan", + ">": "GreaterThan", } { assert.Equal(t, want, SchemaNameToTypeName(in)) } } + +func TestTypeDefinitionsEquivalent(t *testing.T) { + def1 := TypeDefinition{TypeName: "name", Schema: Schema{ + OAPISchema: &openapi3.Schema{}, + }} + def2 := TypeDefinition{TypeName: "name", Schema: Schema{ + OAPISchema: &openapi3.Schema{}, + }} + assert.True(t, TypeDefinitionsEquivalent(def1, def2)) +} + +func TestRefPathToObjName(t *testing.T) { + t.Parallel() + + for in, want := range map[string]string{ + "#/components/schemas/Foo": "Foo", + "#/components/parameters/Bar": "Bar", + "#/components/responses/baz_baz": "baz_baz", + "document.json#/Foo": "Foo", + "http://deepmap.com/schemas/document.json#/objObj": "objObj", + } { + assert.Equal(t, want, RefPathToObjName(in)) + } +} + +func TestLowercaseFirstCharacters(t *testing.T) { + tests := []struct { + name string + in string + expected string + }{ + { + name: "id", + expected: "id", + }, + { + name: "CamelCase", + expected: "camelCase", + }, + { + name: "ID", + expected: "id", + }, + { + name: "DBTree", + expected: "dbTree", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, LowercaseFirstCharacters(tt.name)) + }) + } +} + +func Test_replaceInitialism(t *testing.T) { + type args struct { + s string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "empty string", + args: args{s: ""}, + want: "", + }, + { + name: "no initialism", + args: args{s: "foo"}, + want: "foo", + }, + { + name: "one initialism", + args: args{s: "fooId"}, + want: "fooID", + }, + { + name: "two initialism", + args: args{s: "fooIdBarApi"}, + want: "fooIDBarAPI", + }, + { + name: "already initialism", + args: args{s: "fooIDBarAPI"}, + want: "fooIDBarAPI", + }, + { + name: "one initialism at start", + args: args{s: "idFoo"}, + want: "idFoo", + }, + { + name: "one initialism at start and one in middle", + args: args{s: "apiIdFoo"}, + want: "apiIDFoo", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, replaceInitialism(tt.args.s), "replaceInitialism(%v)", tt.args.s) + }) + } +} diff --git a/pkg/ecdsafile/ecdsafile.go b/pkg/ecdsafile/ecdsafile.go index b5240a1468..fabb3aa3c8 100644 --- a/pkg/ecdsafile/ecdsafile.go +++ b/pkg/ecdsafile/ecdsafile.go @@ -29,7 +29,7 @@ func LoadEcdsaPublicKey(buf []byte) (*ecdsa.PublicKey, error) { // which supports multiple types of keys. keyIface, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { - return nil, fmt.Errorf("Error loading public key: %w", err) + return nil, fmt.Errorf("error loading public key: %w", err) } // Now, we're assuming the key content is ECDSA, and converting. @@ -53,7 +53,7 @@ func LoadEcdsaPrivateKey(buf []byte) (*ecdsa.PrivateKey, error) { // and we're assuming this encoding contains X509 key material. privateKey, err := x509.ParseECPrivateKey(block.Bytes) if err != nil { - return nil, fmt.Errorf("Error loading private ECDSA key: %w", err) + return nil, fmt.Errorf("error loading private ECDSA key: %w", err) } return privateKey, nil } diff --git a/pkg/gin-middleware/oapi_validate.go b/pkg/gin-middleware/oapi_validate.go deleted file mode 100644 index 3406134a68..0000000000 --- a/pkg/gin-middleware/oapi_validate.go +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2021 DeepMap, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package middleware - -import ( - "context" - "errors" - "fmt" - "log" - "net/http" - "os" - "strings" - - "github.com/getkin/kin-openapi/openapi3" - "github.com/getkin/kin-openapi/openapi3filter" - "github.com/getkin/kin-openapi/routers" - "github.com/getkin/kin-openapi/routers/gorillamux" - "github.com/gin-gonic/gin" -) - -const ( - GinContextKey = "oapi-codegen/gin-context" - UserDataKey = "oapi-codegen/user-data" -) - -// OapiValidatorFromYamlFile creates a validator middleware from a YAML file path -func OapiValidatorFromYamlFile(path string) (gin.HandlerFunc, error) { - data, err := os.ReadFile(path) - if err != nil { - return nil, fmt.Errorf("error reading %s: %s", path, err) - } - - swagger, err := openapi3.NewLoader().LoadFromData(data) - if err != nil { - return nil, fmt.Errorf("error parsing %s as Swagger YAML: %s", - path, err) - } - return OapiRequestValidator(swagger), nil -} - -// OapiRequestValidator is an gin middleware function which validates incoming HTTP requests -// to make sure that they conform to the given OAPI 3.0 specification. When -// OAPI validation fails on the request, we return an HTTP/400 with error message -func OapiRequestValidator(swagger *openapi3.T) gin.HandlerFunc { - return OapiRequestValidatorWithOptions(swagger, nil) -} - -// ErrorHandler is called when there is an error in validation -type ErrorHandler func(c *gin.Context, message string, statusCode int) - -// MultiErrorHandler is called when oapi returns a MultiError type -type MultiErrorHandler func(openapi3.MultiError) error - -// Options to customize request validation. These are passed through to -// openapi3filter. -type Options struct { - ErrorHandler ErrorHandler - Options openapi3filter.Options - ParamDecoder openapi3filter.ContentParameterDecoder - UserData interface{} - MultiErrorHandler MultiErrorHandler - // SilenceServersWarning allows silencing a warning for https://github.com/deepmap/oapi-codegen/issues/882 that reports when an OpenAPI spec has `spec.Servers != nil` - SilenceServersWarning bool -} - -// OapiRequestValidatorWithOptions creates a validator from a swagger object, with validation options -func OapiRequestValidatorWithOptions(swagger *openapi3.T, options *Options) gin.HandlerFunc { - if swagger.Servers != nil && (options == nil || options.SilenceServersWarning) { - log.Println("WARN: OapiRequestValidatorWithOptions called with an OpenAPI spec that has `Servers` set. This may lead to an HTTP 400 with `no matching operation was found` when sending a valid request, as the validator performs `Host` header validation. If you're expecting `Host` header validation, you can silence this warning by setting `Options.SilenceServersWarning = true`. See https://github.com/deepmap/oapi-codegen/issues/882 for more information.") - } - - router, err := gorillamux.NewRouter(swagger) - if err != nil { - panic(err) - } - return func(c *gin.Context) { - err := ValidateRequestFromContext(c, router, options) - if err != nil { - if options != nil && options.ErrorHandler != nil { - options.ErrorHandler(c, err.Error(), http.StatusBadRequest) - // in case the handler didn't internally call Abort, stop the chain - c.Abort() - } else { - // note: i am not sure if this is the best way to handle this - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - } - } - c.Next() - } -} - -// ValidateRequestFromContext is called from the middleware above and actually does the work -// of validating a request. -func ValidateRequestFromContext(c *gin.Context, router routers.Router, options *Options) error { - req := c.Request - route, pathParams, err := router.FindRoute(req) - - // We failed to find a matching route for the request. - if err != nil { - switch e := err.(type) { - case *routers.RouteError: - // We've got a bad request, the path requested doesn't match - // either server, or path, or something. - return errors.New(e.Reason) - default: - // This should never happen today, but if our upstream code changes, - // we don't want to crash the server, so handle the unexpected error. - return fmt.Errorf("error validating route: %s", err.Error()) - } - } - - validationInput := &openapi3filter.RequestValidationInput{ - Request: req, - PathParams: pathParams, - Route: route, - } - - // Pass the gin context into the request validator, so that any callbacks - // which it invokes make it available. - requestContext := context.WithValue(context.Background(), GinContextKey, c) //nolint:staticcheck - - if options != nil { - validationInput.Options = &options.Options - validationInput.ParamDecoder = options.ParamDecoder - requestContext = context.WithValue(requestContext, UserDataKey, options.UserData) //nolint:staticcheck - } - - err = openapi3filter.ValidateRequest(requestContext, validationInput) - if err != nil { - me := openapi3.MultiError{} - if errors.As(err, &me) { - errFunc := getMultiErrorHandlerFromOptions(options) - return errFunc(me) - } - - switch e := err.(type) { - case *openapi3filter.RequestError: - // We've got a bad request - // Split up the verbose error by lines and return the first one - // openapi errors seem to be multi-line with a decent message on the first - errorLines := strings.Split(e.Error(), "\n") - return fmt.Errorf("error in openapi3filter.RequestError: %s", errorLines[0]) - case *openapi3filter.SecurityRequirementsError: - return fmt.Errorf("error in openapi3filter.SecurityRequirementsError: %s", e.Error()) - default: - // This should never happen today, but if our upstream code changes, - // we don't want to crash the server, so handle the unexpected error. - return fmt.Errorf("error validating request: %s", err) - } - } - return nil -} - -// GetGinContext gets the echo context from within requests. It returns -// nil if not found or wrong type. -func GetGinContext(c context.Context) *gin.Context { - iface := c.Value(GinContextKey) - if iface == nil { - return nil - } - ginCtx, ok := iface.(*gin.Context) - if !ok { - return nil - } - return ginCtx -} - -func GetUserData(c context.Context) interface{} { - return c.Value(UserDataKey) -} - -// attempt to get the MultiErrorHandler from the options. If it is not set, -// return a default handler -func getMultiErrorHandlerFromOptions(options *Options) MultiErrorHandler { - if options == nil { - return defaultMultiErrorHandler - } - - if options.MultiErrorHandler == nil { - return defaultMultiErrorHandler - } - - return options.MultiErrorHandler -} - -// defaultMultiErrorHandler returns a StatusBadRequest (400) and a list -// of all of the errors. This method is called if there are no other -// methods defined on the options. -func defaultMultiErrorHandler(me openapi3.MultiError) error { - return fmt.Errorf("multiple errors encountered: %s", me) -} diff --git a/pkg/gin-middleware/oapi_validate_test.go b/pkg/gin-middleware/oapi_validate_test.go deleted file mode 100644 index 30ad57fc20..0000000000 --- a/pkg/gin-middleware/oapi_validate_test.go +++ /dev/null @@ -1,414 +0,0 @@ -// Copyright 2021 DeepMap, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package middleware - -import ( - "context" - _ "embed" - "errors" - "fmt" - "io" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/deepmap/oapi-codegen/pkg/testutil" - "github.com/getkin/kin-openapi/openapi3" - "github.com/getkin/kin-openapi/openapi3filter" - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -//go:embed test_spec.yaml -var testSchema []byte - -func doGet(t *testing.T, handler http.Handler, rawURL string) *httptest.ResponseRecorder { - u, err := url.Parse(rawURL) - if err != nil { - t.Fatalf("Invalid url: %s", rawURL) - } - - response := testutil.NewRequest().Get(u.RequestURI()).WithHost(u.Host).WithAcceptJson().GoWithHTTPHandler(t, handler) - return response.Recorder -} - -func doPost(t *testing.T, handler http.Handler, rawURL string, jsonBody interface{}) *httptest.ResponseRecorder { - u, err := url.Parse(rawURL) - if err != nil { - t.Fatalf("Invalid url: %s", rawURL) - } - - response := testutil.NewRequest().Post(u.RequestURI()).WithHost(u.Host).WithJsonBody(jsonBody).GoWithHTTPHandler(t, handler) - return response.Recorder -} - -func TestOapiRequestValidator(t *testing.T) { - swagger, err := openapi3.NewLoader().LoadFromData(testSchema) - require.NoError(t, err, "Error initializing swagger") - - // Create a new echo router - g := gin.New() - - // Set up an authenticator to check authenticated function. It will allow - // access to "someScope", but disallow others. - options := Options{ - ErrorHandler: func(c *gin.Context, message string, statusCode int) { - c.String(statusCode, "test: "+message) - }, - Options: openapi3filter.Options{ - AuthenticationFunc: func(c context.Context, input *openapi3filter.AuthenticationInput) error { - // The gin context should be propagated into here. - gCtx := GetGinContext(c) - assert.NotNil(t, gCtx) - // As should user data - assert.EqualValues(t, "hi!", GetUserData(c)) - - for _, s := range input.Scopes { - if s == "someScope" { - return nil - } - if s == "unauthorized" { - return errors.New("unauthorized") - } - } - return errors.New("forbidden") - }, - }, - UserData: "hi!", - } - - // Install our OpenApi based request validator - g.Use(OapiRequestValidatorWithOptions(swagger, &options)) - - called := false - - // Install a request handler for /resource. We want to make sure it doesn't - // get called. - g.GET("/resource", func(c *gin.Context) { - called = true - }) - // Let's send the request to the wrong server, this should fail validation - { - rec := doGet(t, g, "http://not.deepmap.ai/resource") - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.False(t, called, "Handler should not have been called") - } - - // Let's send a good request, it should pass - { - rec := doGet(t, g, "http://deepmap.ai/resource") - assert.Equal(t, http.StatusOK, rec.Code) - assert.True(t, called, "Handler should have been called") - called = false - } - - // Send an out-of-spec parameter - { - rec := doGet(t, g, "http://deepmap.ai/resource?id=500") - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Send a bad parameter type - { - rec := doGet(t, g, "http://deepmap.ai/resource?id=foo") - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Add a handler for the POST message - g.POST("/resource", func(c *gin.Context) { - called = true - c.AbortWithStatus(http.StatusNoContent) - }) - - called = false - // Send a good request body - { - body := struct { - Name string `json:"name"` - }{ - Name: "Marcin", - } - rec := doPost(t, g, "http://deepmap.ai/resource", body) - assert.Equal(t, http.StatusNoContent, rec.Code) - assert.True(t, called, "Handler should have been called") - called = false - } - - // Send a malformed body - { - body := struct { - Name int `json:"name"` - }{ - Name: 7, - } - rec := doPost(t, g, "http://deepmap.ai/resource", body) - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.False(t, called, "Handler should not have been called") - called = false - } - - g.GET("/protected_resource", func(c *gin.Context) { - called = true - c.AbortWithStatus(http.StatusNoContent) - }) - - // Call a protected function to which we have access - { - rec := doGet(t, g, "http://deepmap.ai/protected_resource") - assert.Equal(t, http.StatusNoContent, rec.Code) - assert.True(t, called, "Handler should have been called") - called = false - } - - g.GET("/protected_resource2", func(c *gin.Context) { - called = true - c.AbortWithStatus(http.StatusNoContent) - }) - // Call a protected function to which we don't have access - { - rec := doGet(t, g, "http://deepmap.ai/protected_resource2") - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.False(t, called, "Handler should not have been called") - called = false - } - - g.GET("/protected_resource_401", func(c *gin.Context) { - called = true - c.AbortWithStatus(http.StatusNoContent) - }) - // Call a protected function without credentials - { - rec := doGet(t, g, "http://deepmap.ai/protected_resource_401") - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.Equal(t, "test: error in openapi3filter.SecurityRequirementsError: security requirements failed: unauthorized", rec.Body.String()) - assert.False(t, called, "Handler should not have been called") - called = false - } -} - -func TestOapiRequestValidatorWithOptionsMultiError(t *testing.T) { - swagger, err := openapi3.NewLoader().LoadFromData(testSchema) - require.NoError(t, err, "Error initializing swagger") - - g := gin.New() - - // Set up an authenticator to check authenticated function. It will allow - // access to "someScope", but disallow others. - options := Options{ - Options: openapi3filter.Options{ - ExcludeRequestBody: false, - ExcludeResponseBody: false, - IncludeResponseStatus: true, - MultiError: true, - }, - } - - // register middleware - g.Use(OapiRequestValidatorWithOptions(swagger, &options)) - - called := false - - // Install a request handler for /resource. We want to make sure it doesn't - // get called. - g.GET("/multiparamresource", func(c *gin.Context) { - called = true - }) - - // Let's send a good request, it should pass - { - rec := doGet(t, g, "http://deepmap.ai/multiparamresource?id=50&id2=50") - assert.Equal(t, http.StatusOK, rec.Code) - assert.True(t, called, "Handler should have been called") - called = false - } - - // Let's send a request with a missing parameter, it should return - // a bad status - { - rec := doGet(t, g, "http://deepmap.ai/multiparamresource?id=50") - assert.Equal(t, http.StatusBadRequest, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "multiple errors encountered") - assert.Contains(t, string(body), "parameter \\\"id2\\\"") - assert.Contains(t, string(body), "value is required but missing") - } - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Let's send a request with a 2 missing parameters, it should return - // a bad status - { - rec := doGet(t, g, "http://deepmap.ai/multiparamresource") - assert.Equal(t, http.StatusBadRequest, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "multiple errors encountered") - assert.Contains(t, string(body), "parameter \\\"id\\\"") - assert.Contains(t, string(body), "value is required but missing") - assert.Contains(t, string(body), "parameter \\\"id2\\\"") - assert.Contains(t, string(body), "value is required but missing") - } - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Let's send a request with a 1 missing parameter, and another outside - // or the parameters. It should return a bad status - { - rec := doGet(t, g, "http://deepmap.ai/multiparamresource?id=500") - assert.Equal(t, http.StatusBadRequest, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "multiple errors encountered") - assert.Contains(t, string(body), "parameter \\\"id\\\"") - assert.Contains(t, string(body), "number must be at most 100") - assert.Contains(t, string(body), "parameter \\\"id2\\\"") - assert.Contains(t, string(body), "value is required but missing") - } - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Let's send a request with a parameters that do not meet spec. It should - // return a bad status - { - rec := doGet(t, g, "http://deepmap.ai/multiparamresource?id=abc&id2=1") - assert.Equal(t, http.StatusBadRequest, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "multiple errors encountered") - assert.Contains(t, string(body), "parameter \\\"id\\\"") - assert.Contains(t, string(body), "value abc: an invalid integer: invalid syntax") - assert.Contains(t, string(body), "parameter \\\"id2\\\"") - assert.Contains(t, string(body), "number must be at least 10") - } - assert.False(t, called, "Handler should not have been called") - called = false - } -} - -func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) { - swagger, err := openapi3.NewLoader().LoadFromData(testSchema) - require.NoError(t, err, "Error initializing swagger") - - g := gin.New() - - // Set up an authenticator to check authenticated function. It will allow - // access to "someScope", but disallow others. - options := Options{ - Options: openapi3filter.Options{ - ExcludeRequestBody: false, - ExcludeResponseBody: false, - IncludeResponseStatus: true, - MultiError: true, - }, - MultiErrorHandler: func(me openapi3.MultiError) error { - return fmt.Errorf("Bad stuff - %s", me.Error()) - }, - } - - // register middleware - g.Use(OapiRequestValidatorWithOptions(swagger, &options)) - - called := false - - // Install a request handler for /resource. We want to make sure it doesn't - // get called. - g.GET("/multiparamresource", func(c *gin.Context) { - called = true - }) - - // Let's send a good request, it should pass - { - rec := doGet(t, g, "http://deepmap.ai/multiparamresource?id=50&id2=50") - assert.Equal(t, http.StatusOK, rec.Code) - assert.True(t, called, "Handler should have been called") - called = false - } - - // Let's send a request with a missing parameter, it should return - // a bad status - { - rec := doGet(t, g, "http://deepmap.ai/multiparamresource?id=50") - assert.Equal(t, http.StatusBadRequest, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "Bad stuff") - assert.Contains(t, string(body), "parameter \\\"id2\\\"") - assert.Contains(t, string(body), "value is required but missing") - } - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Let's send a request with a 2 missing parameters, it should return - // a bad status - { - rec := doGet(t, g, "http://deepmap.ai/multiparamresource") - assert.Equal(t, http.StatusBadRequest, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "Bad stuff") - assert.Contains(t, string(body), "parameter \\\"id\\\"") - assert.Contains(t, string(body), "value is required but missing") - assert.Contains(t, string(body), "parameter \\\"id2\\\"") - assert.Contains(t, string(body), "value is required but missing") - } - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Let's send a request with a 1 missing parameter, and another outside - // or the parameters. It should return a bad status - { - rec := doGet(t, g, "http://deepmap.ai/multiparamresource?id=500") - assert.Equal(t, http.StatusBadRequest, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "Bad stuff") - assert.Contains(t, string(body), "parameter \\\"id\\\"") - assert.Contains(t, string(body), "number must be at most 100") - assert.Contains(t, string(body), "parameter \\\"id2\\\"") - assert.Contains(t, string(body), "value is required but missing") - } - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Let's send a request with a parameters that do not meet spec. It should - // return a bad status - { - rec := doGet(t, g, "http://deepmap.ai/multiparamresource?id=abc&id2=1") - assert.Equal(t, http.StatusBadRequest, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "Bad stuff") - assert.Contains(t, string(body), "parameter \\\"id\\\"") - assert.Contains(t, string(body), "value abc: an invalid integer: invalid syntax") - assert.Contains(t, string(body), "parameter \\\"id2\\\"") - assert.Contains(t, string(body), "number must be at least 10") - } - assert.False(t, called, "Handler should not have been called") - called = false - } -} diff --git a/pkg/gin-middleware/test_spec.yaml b/pkg/gin-middleware/test_spec.yaml deleted file mode 100644 index 6e0a2415d2..0000000000 --- a/pkg/gin-middleware/test_spec.yaml +++ /dev/null @@ -1,103 +0,0 @@ -openapi: "3.0.0" -info: - version: 1.0.0 - title: TestServer -servers: - - url: http://deepmap.ai/ -paths: - /resource: - get: - operationId: getResource - parameters: - - name: id - in: query - schema: - type: integer - minimum: 10 - maximum: 100 - responses: - '200': - description: success - content: - application/json: - schema: - properties: - name: - type: string - id: - type: integer - post: - operationId: createResource - responses: - '204': - description: No content - requestBody: - required: true - content: - application/json: - schema: - properties: - name: - type: string - /protected_resource: - get: - operationId: getProtectedResource - security: - - BearerAuth: - - someScope - responses: - '204': - description: no content - /protected_resource2: - get: - operationId: getProtectedResource - security: - - BearerAuth: - - otherScope - responses: - '204': - description: no content - /protected_resource_401: - get: - operationId: getProtectedResource - security: - - BearerAuth: - - unauthorized - responses: - '401': - description: no content - /multiparamresource: - get: - operationId: getResource - parameters: - - name: id - in: query - required: true - schema: - type: integer - minimum: 10 - maximum: 100 - - name: id2 - required: true - in: query - schema: - type: integer - minimum: 10 - maximum: 100 - responses: - '200': - description: success - content: - application/json: - schema: - properties: - name: - type: string - id: - type: integer -components: - securitySchemes: - BearerAuth: - type: http - scheme: bearer - bearerFormat: JWT diff --git a/pkg/middleware/oapi_validate.go b/pkg/middleware/oapi_validate.go deleted file mode 100644 index 0318d29610..0000000000 --- a/pkg/middleware/oapi_validate.go +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright 2019 DeepMap, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package middleware - -import ( - "context" - "errors" - "fmt" - "log" - "net/http" - "os" - "strings" - - "github.com/getkin/kin-openapi/openapi3" - "github.com/getkin/kin-openapi/openapi3filter" - "github.com/getkin/kin-openapi/routers" - "github.com/getkin/kin-openapi/routers/gorillamux" - "github.com/labstack/echo/v4" - echomiddleware "github.com/labstack/echo/v4/middleware" -) - -const ( - EchoContextKey = "oapi-codegen/echo-context" - UserDataKey = "oapi-codegen/user-data" -) - -// OapiValidatorFromYamlFile is an Echo middleware function which validates incoming HTTP requests -// to make sure that they conform to the given OAPI 3.0 specification. When -// OAPI validation fails on the request, we return an HTTP/400. -// Create validator middleware from a YAML file path -func OapiValidatorFromYamlFile(path string) (echo.MiddlewareFunc, error) { - data, err := os.ReadFile(path) - if err != nil { - return nil, fmt.Errorf("error reading %s: %s", path, err) - } - - swagger, err := openapi3.NewLoader().LoadFromData(data) - if err != nil { - return nil, fmt.Errorf("error parsing %s as Swagger YAML: %s", - path, err) - } - return OapiRequestValidator(swagger), nil -} - -// OapiRequestValidator creates a validator from a swagger object. -func OapiRequestValidator(swagger *openapi3.T) echo.MiddlewareFunc { - return OapiRequestValidatorWithOptions(swagger, nil) -} - -// ErrorHandler is called when there is an error in validation -type ErrorHandler func(c echo.Context, err *echo.HTTPError) error - -// MultiErrorHandler is called when oapi returns a MultiError type -type MultiErrorHandler func(openapi3.MultiError) *echo.HTTPError - -// Options to customize request validation. These are passed through to -// openapi3filter. -type Options struct { - ErrorHandler ErrorHandler - Options openapi3filter.Options - ParamDecoder openapi3filter.ContentParameterDecoder - UserData interface{} - Skipper echomiddleware.Skipper - MultiErrorHandler MultiErrorHandler - // SilenceServersWarning allows silencing a warning for https://github.com/deepmap/oapi-codegen/issues/882 that reports when an OpenAPI spec has `spec.Servers != nil` - SilenceServersWarning bool -} - -// OapiRequestValidatorWithOptions creates a validator from a swagger object, with validation options -func OapiRequestValidatorWithOptions(swagger *openapi3.T, options *Options) echo.MiddlewareFunc { - if swagger.Servers != nil && (options == nil || options.SilenceServersWarning) { - log.Println("WARN: OapiRequestValidatorWithOptions called with an OpenAPI spec that has `Servers` set. This may lead to an HTTP 400 with `no matching operation was found` when sending a valid request, as the validator performs `Host` header validation. If you're expecting `Host` header validation, you can silence this warning by setting `Options.SilenceServersWarning = true`. See https://github.com/deepmap/oapi-codegen/issues/882 for more information.") - } - - router, err := gorillamux.NewRouter(swagger) - if err != nil { - panic(err) - } - - skipper := getSkipperFromOptions(options) - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - if skipper(c) { - return next(c) - } - - err := ValidateRequestFromContext(c, router, options) - if err != nil { - if options != nil && options.ErrorHandler != nil { - return options.ErrorHandler(c, err) - } - return err - } - return next(c) - } - } -} - -// ValidateRequestFromContext is called from the middleware above and actually does the work -// of validating a request. -func ValidateRequestFromContext(ctx echo.Context, router routers.Router, options *Options) *echo.HTTPError { - req := ctx.Request() - route, pathParams, err := router.FindRoute(req) - - // We failed to find a matching route for the request. - if err != nil { - switch e := err.(type) { - case *routers.RouteError: - // We've got a bad request, the path requested doesn't match - // either server, or path, or something. - return echo.NewHTTPError(http.StatusBadRequest, e.Reason) - default: - // This should never happen today, but if our upstream code changes, - // we don't want to crash the server, so handle the unexpected error. - return echo.NewHTTPError(http.StatusInternalServerError, - fmt.Sprintf("error validating route: %s", err.Error())) - } - } - - validationInput := &openapi3filter.RequestValidationInput{ - Request: req, - PathParams: pathParams, - Route: route, - } - - // Pass the Echo context into the request validator, so that any callbacks - // which it invokes make it available. - requestContext := context.WithValue(context.Background(), EchoContextKey, ctx) //nolint:staticcheck - - if options != nil { - validationInput.Options = &options.Options - validationInput.ParamDecoder = options.ParamDecoder - requestContext = context.WithValue(requestContext, UserDataKey, options.UserData) //nolint:staticcheck - } - - err = openapi3filter.ValidateRequest(requestContext, validationInput) - if err != nil { - me := openapi3.MultiError{} - if errors.As(err, &me) { - errFunc := getMultiErrorHandlerFromOptions(options) - return errFunc(me) - } - - switch e := err.(type) { - case *openapi3filter.RequestError: - // We've got a bad request - // Split up the verbose error by lines and return the first one - // openapi errors seem to be multi-line with a decent message on the first - errorLines := strings.Split(e.Error(), "\n") - return &echo.HTTPError{ - Code: http.StatusBadRequest, - Message: errorLines[0], - Internal: err, - } - case *openapi3filter.SecurityRequirementsError: - for _, err := range e.Errors { - httpErr, ok := err.(*echo.HTTPError) - if ok { - return httpErr - } - } - return &echo.HTTPError{ - Code: http.StatusForbidden, - Message: e.Error(), - Internal: err, - } - default: - // This should never happen today, but if our upstream code changes, - // we don't want to crash the server, so handle the unexpected error. - return &echo.HTTPError{ - Code: http.StatusInternalServerError, - Message: fmt.Sprintf("error validating request: %s", err), - Internal: err, - } - } - } - return nil -} - -// GetEchoContext gets the echo context from within requests. It returns -// nil if not found or wrong type. -func GetEchoContext(c context.Context) echo.Context { - iface := c.Value(EchoContextKey) - if iface == nil { - return nil - } - eCtx, ok := iface.(echo.Context) - if !ok { - return nil - } - return eCtx -} - -func GetUserData(c context.Context) interface{} { - return c.Value(UserDataKey) -} - -// attempt to get the skipper from the options whether it is set or not -func getSkipperFromOptions(options *Options) echomiddleware.Skipper { - if options == nil { - return echomiddleware.DefaultSkipper - } - - if options.Skipper == nil { - return echomiddleware.DefaultSkipper - } - - return options.Skipper -} - -// attempt to get the MultiErrorHandler from the options. If it is not set, -// return a default handler -func getMultiErrorHandlerFromOptions(options *Options) MultiErrorHandler { - if options == nil { - return defaultMultiErrorHandler - } - - if options.MultiErrorHandler == nil { - return defaultMultiErrorHandler - } - - return options.MultiErrorHandler -} - -// defaultMultiErrorHandler returns a StatusBadRequest (400) and a list -// of all of the errors. This method is called if there are no other -// methods defined on the options. -func defaultMultiErrorHandler(me openapi3.MultiError) *echo.HTTPError { - return &echo.HTTPError{ - Code: http.StatusBadRequest, - Message: me.Error(), - Internal: me, - } -} diff --git a/pkg/middleware/oapi_validate_test.go b/pkg/middleware/oapi_validate_test.go deleted file mode 100644 index 8eb53094ac..0000000000 --- a/pkg/middleware/oapi_validate_test.go +++ /dev/null @@ -1,430 +0,0 @@ -// Copyright 2019 DeepMap, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package middleware - -import ( - "context" - _ "embed" - "errors" - "io" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/deepmap/oapi-codegen/pkg/testutil" - "github.com/getkin/kin-openapi/openapi3" - "github.com/getkin/kin-openapi/openapi3filter" - "github.com/labstack/echo/v4" - echomiddleware "github.com/labstack/echo/v4/middleware" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -//go:embed test_spec.yaml -var testSchema []byte - -func doGet(t *testing.T, e *echo.Echo, rawURL string) *httptest.ResponseRecorder { - u, err := url.Parse(rawURL) - if err != nil { - t.Fatalf("Invalid url: %s", rawURL) - } - - response := testutil.NewRequest().Get(u.RequestURI()).WithHost(u.Host).WithAcceptJson().GoWithHTTPHandler(t, e) - return response.Recorder -} - -func doPost(t *testing.T, e *echo.Echo, rawURL string, jsonBody interface{}) *httptest.ResponseRecorder { - u, err := url.Parse(rawURL) - if err != nil { - t.Fatalf("Invalid url: %s", rawURL) - } - - response := testutil.NewRequest().Post(u.RequestURI()).WithHost(u.Host).WithJsonBody(jsonBody).GoWithHTTPHandler(t, e) - return response.Recorder -} - -func TestOapiRequestValidator(t *testing.T) { - swagger, err := openapi3.NewLoader().LoadFromData(testSchema) - require.NoError(t, err, "Error initializing swagger") - - // Create a new echo router - e := echo.New() - - // Set up an authenticator to check authenticated function. It will allow - // access to "someScope", but disallow others. - options := Options{ - ErrorHandler: func(c echo.Context, err *echo.HTTPError) error { - return c.String(err.Code, "test: "+err.Error()) - }, - Options: openapi3filter.Options{ - AuthenticationFunc: func(c context.Context, input *openapi3filter.AuthenticationInput) error { - // The echo context should be propagated into here. - eCtx := GetEchoContext(c) - assert.NotNil(t, eCtx) - // As should user data - assert.EqualValues(t, "hi!", GetUserData(c)) - - for _, s := range input.Scopes { - if s == "someScope" { - return nil - } - if s == "unauthorized" { - return echo.ErrUnauthorized - } - } - return errors.New("forbidden") - }, - }, - UserData: "hi!", - } - - // Install our OpenApi based request validator - e.Use(OapiRequestValidatorWithOptions(swagger, &options)) - - called := false - - // Install a request handler for /resource. We want to make sure it doesn't - // get called. - e.GET("/resource", func(c echo.Context) error { - called = true - return nil - }) - // Let's send the request to the wrong server, this should fail validation - { - rec := doGet(t, e, "http://not.deepmap.ai/resource") - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.False(t, called, "Handler should not have been called") - } - - // Let's send a good request, it should pass - { - rec := doGet(t, e, "http://deepmap.ai/resource") - assert.Equal(t, http.StatusOK, rec.Code) - assert.True(t, called, "Handler should have been called") - called = false - } - - // Send an out-of-spec parameter - { - rec := doGet(t, e, "http://deepmap.ai/resource?id=500") - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Send a bad parameter type - { - rec := doGet(t, e, "http://deepmap.ai/resource?id=foo") - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Add a handler for the POST message - e.POST("/resource", func(c echo.Context) error { - called = true - return c.NoContent(http.StatusNoContent) - }) - - called = false - // Send a good request body - { - body := struct { - Name string `json:"name"` - }{ - Name: "Marcin", - } - rec := doPost(t, e, "http://deepmap.ai/resource", body) - assert.Equal(t, http.StatusNoContent, rec.Code) - assert.True(t, called, "Handler should have been called") - called = false - } - - // Send a malformed body - { - body := struct { - Name int `json:"name"` - }{ - Name: 7, - } - rec := doPost(t, e, "http://deepmap.ai/resource", body) - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.False(t, called, "Handler should not have been called") - called = false - } - - e.GET("/protected_resource", func(c echo.Context) error { - called = true - return c.NoContent(http.StatusNoContent) - - }) - - // Call a protected function to which we have access - { - rec := doGet(t, e, "http://deepmap.ai/protected_resource") - assert.Equal(t, http.StatusNoContent, rec.Code) - assert.True(t, called, "Handler should have been called") - called = false - } - - e.GET("/protected_resource2", func(c echo.Context) error { - called = true - return c.NoContent(http.StatusNoContent) - }) - // Call a protected function to which we dont have access - { - rec := doGet(t, e, "http://deepmap.ai/protected_resource2") - assert.Equal(t, http.StatusForbidden, rec.Code) - assert.False(t, called, "Handler should not have been called") - called = false - } - - e.GET("/protected_resource_401", func(c echo.Context) error { - called = true - return c.NoContent(http.StatusNoContent) - }) - // Call a protected function without credentials - { - rec := doGet(t, e, "http://deepmap.ai/protected_resource_401") - assert.Equal(t, http.StatusUnauthorized, rec.Code) - assert.Equal(t, "test: code=401, message=Unauthorized", rec.Body.String()) - assert.False(t, called, "Handler should not have been called") - called = false - } -} - -func TestOapiRequestValidatorWithOptionsMultiError(t *testing.T) { - swagger, err := openapi3.NewLoader().LoadFromData(testSchema) - require.NoError(t, err, "Error initializing swagger") - - // Create a new echo router - e := echo.New() - - // Set up an authenticator to check authenticated function. It will allow - // access to "someScope", but disallow others. - options := Options{ - Options: openapi3filter.Options{ - ExcludeRequestBody: false, - ExcludeResponseBody: false, - IncludeResponseStatus: true, - MultiError: true, - }, - } - - // register middleware - e.Use(OapiRequestValidatorWithOptions(swagger, &options)) - - called := false - - // Install a request handler for /resource. We want to make sure it doesn't - // get called. - e.GET("/multiparamresource", func(c echo.Context) error { - called = true - return nil - }) - - // Let's send a good request, it should pass - { - rec := doGet(t, e, "http://deepmap.ai/multiparamresource?id=50&id2=50") - assert.Equal(t, http.StatusOK, rec.Code) - assert.True(t, called, "Handler should have been called") - called = false - } - - // Let's send a request with a missing parameter, it should return - // a bad status - { - rec := doGet(t, e, "http://deepmap.ai/multiparamresource?id=50") - assert.Equal(t, http.StatusBadRequest, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "parameter \\\"id2\\\"") - assert.Contains(t, string(body), "value is required but missing") - } - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Let's send a request with a 2 missing parameters, it should return - // a bad status - { - rec := doGet(t, e, "http://deepmap.ai/multiparamresource") - assert.Equal(t, http.StatusBadRequest, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "parameter \\\"id\\\"") - assert.Contains(t, string(body), "value is required but missing") - assert.Contains(t, string(body), "parameter \\\"id2\\\"") - assert.Contains(t, string(body), "value is required but missing") - } - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Let's send a request with a 1 missing parameter, and another outside - // or the parameters. It should return a bad status - { - rec := doGet(t, e, "http://deepmap.ai/multiparamresource?id=500") - assert.Equal(t, http.StatusBadRequest, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "parameter \\\"id\\\"") - assert.Contains(t, string(body), "number must be at most 100") - assert.Contains(t, string(body), "parameter \\\"id2\\\"") - assert.Contains(t, string(body), "value is required but missing") - } - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Let's send a request with a parameters that do not meet spec. It should - // return a bad status - { - rec := doGet(t, e, "http://deepmap.ai/multiparamresource?id=abc&id2=1") - assert.Equal(t, http.StatusBadRequest, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "parameter \\\"id\\\"") - assert.Contains(t, string(body), "value abc: an invalid integer: invalid syntax") - assert.Contains(t, string(body), "parameter \\\"id2\\\"") - assert.Contains(t, string(body), "number must be at least 10") - } - assert.False(t, called, "Handler should not have been called") - called = false - } -} - -func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) { - swagger, err := openapi3.NewLoader().LoadFromData(testSchema) - require.NoError(t, err, "Error initializing swagger") - - // Create a new echo router - e := echo.New() - - // Set up an authenticator to check authenticated function. It will allow - // access to "someScope", but disallow others. - options := Options{ - Options: openapi3filter.Options{ - ExcludeRequestBody: false, - ExcludeResponseBody: false, - IncludeResponseStatus: true, - MultiError: true, - }, - MultiErrorHandler: func(me openapi3.MultiError) *echo.HTTPError { - return &echo.HTTPError{ - Code: http.StatusTeapot, - Message: me.Error(), - Internal: me, - } - }, - } - - // register middleware - e.Use(OapiRequestValidatorWithOptions(swagger, &options)) - - called := false - - // Install a request handler for /resource. We want to make sure it doesn't - // get called. - e.GET("/multiparamresource", func(c echo.Context) error { - called = true - return nil - }) - - // Let's send a good request, it should pass - { - rec := doGet(t, e, "http://deepmap.ai/multiparamresource?id=50&id2=50") - assert.Equal(t, http.StatusOK, rec.Code) - assert.True(t, called, "Handler should have been called") - called = false - } - - // Let's send a request with a missing parameter, it should return - // a bad status - { - rec := doGet(t, e, "http://deepmap.ai/multiparamresource?id=50") - assert.Equal(t, http.StatusTeapot, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "parameter \\\"id2\\\"") - assert.Contains(t, string(body), "value is required but missing") - } - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Let's send a request with a 2 missing parameters, it should return - // a bad status - { - rec := doGet(t, e, "http://deepmap.ai/multiparamresource") - assert.Equal(t, http.StatusTeapot, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "parameter \\\"id\\\"") - assert.Contains(t, string(body), "value is required but missing") - assert.Contains(t, string(body), "parameter \\\"id2\\\"") - assert.Contains(t, string(body), "value is required but missing") - } - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Let's send a request with a 1 missing parameter, and another outside - // or the parameters. It should return a bad status - { - rec := doGet(t, e, "http://deepmap.ai/multiparamresource?id=500") - assert.Equal(t, http.StatusTeapot, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "parameter \\\"id\\\"") - assert.Contains(t, string(body), "number must be at most 100") - assert.Contains(t, string(body), "parameter \\\"id2\\\"") - assert.Contains(t, string(body), "value is required but missing") - } - assert.False(t, called, "Handler should not have been called") - called = false - } - - // Let's send a request with a parameters that do not meet spec. It should - // return a bad status - { - rec := doGet(t, e, "http://deepmap.ai/multiparamresource?id=abc&id2=1") - assert.Equal(t, http.StatusTeapot, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "parameter \\\"id\\\"") - assert.Contains(t, string(body), "value abc: an invalid integer: invalid syntax") - assert.Contains(t, string(body), "parameter \\\"id2\\\"") - assert.Contains(t, string(body), "number must be at least 10") - } - assert.False(t, called, "Handler should not have been called") - called = false - } -} - -func TestGetSkipperFromOptions(t *testing.T) { - - options := new(Options) - assert.NotNil(t, getSkipperFromOptions(options)) - - options = &Options{} - assert.NotNil(t, getSkipperFromOptions(options)) - - options = &Options{ - Skipper: echomiddleware.DefaultSkipper, - } - assert.NotNil(t, getSkipperFromOptions(options)) -} diff --git a/pkg/middleware/test_spec.yaml b/pkg/middleware/test_spec.yaml deleted file mode 100644 index 1f847d756a..0000000000 --- a/pkg/middleware/test_spec.yaml +++ /dev/null @@ -1,103 +0,0 @@ -openapi: "3.0.0" -info: - version: 1.0.0 - title: TestServer -servers: - - url: http://deepmap.ai -paths: - /resource: - get: - operationId: getResource - parameters: - - name: id - in: query - schema: - type: integer - minimum: 10 - maximum: 100 - responses: - '200': - description: success - content: - application/json: - schema: - properties: - name: - type: string - id: - type: integer - post: - operationId: createResource - responses: - '204': - description: No content - requestBody: - required: true - content: - application/json: - schema: - properties: - name: - type: string - /protected_resource: - get: - operationId: getProtectedResource - security: - - BearerAuth: - - someScope - responses: - '204': - description: no content - /protected_resource2: - get: - operationId: getProtectedResource - security: - - BearerAuth: - - otherScope - responses: - '204': - description: no content - /protected_resource_401: - get: - operationId: getProtectedResource - security: - - BearerAuth: - - unauthorized - responses: - '401': - description: no content - /multiparamresource: - get: - operationId: getResource - parameters: - - name: id - in: query - required: true - schema: - type: integer - minimum: 10 - maximum: 100 - - name: id2 - required: true - in: query - schema: - type: integer - minimum: 10 - maximum: 100 - responses: - '200': - description: success - content: - application/json: - schema: - properties: - name: - type: string - id: - type: integer -components: - securitySchemes: - BearerAuth: - type: http - scheme: bearer - bearerFormat: JWT diff --git a/pkg/runtime/bind.go b/pkg/runtime/bind.go deleted file mode 100644 index 1551250d21..0000000000 --- a/pkg/runtime/bind.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2021 DeepMap, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package runtime - -// Binder is the interface implemented by types that can be bound to a query string or a parameter string -// The input can be assumed to be a valid string. If you define a Bind method you are responsible for all -// data being completely bound to the type. -// -// By convention, to approximate the behavior of Bind functions themselves, -// Binder implements Bind("") as a no-op. -type Binder interface { - Bind(src string) error -} diff --git a/pkg/runtime/bindform.go b/pkg/runtime/bindform.go deleted file mode 100644 index 4f4fb76c67..0000000000 --- a/pkg/runtime/bindform.go +++ /dev/null @@ -1,309 +0,0 @@ -package runtime - -import ( - "encoding/json" - "errors" - "fmt" - "mime/multipart" - "net/url" - "reflect" - "strconv" - "strings" - - "github.com/deepmap/oapi-codegen/pkg/types" -) - -const tagName = "json" -const jsonContentType = "application/json" - -type RequestBodyEncoding struct { - ContentType string - Style string - Explode *bool -} - -func BindMultipart(ptr interface{}, reader multipart.Reader) error { - const defaultMemory = 32 << 20 - form, err := reader.ReadForm(defaultMemory) - if err != nil { - return err - } - return BindForm(ptr, form.Value, form.File, nil) -} - -func BindForm(ptr interface{}, form map[string][]string, files map[string][]*multipart.FileHeader, encodings map[string]RequestBodyEncoding) error { - ptrVal := reflect.Indirect(reflect.ValueOf(ptr)) - if ptrVal.Kind() != reflect.Struct { - return errors.New("form data body should be a struct") - } - tValue := ptrVal.Type() - - for i := 0; i < tValue.NumField(); i++ { - field := ptrVal.Field(i) - tag := tValue.Field(i).Tag.Get(tagName) - if !field.CanInterface() || tag == "-" { - continue - } - tag = strings.Split(tag, ",")[0] // extract the name of the tag - if encoding, ok := encodings[tag]; ok { - // custom encoding - values := form[tag] - if len(values) == 0 { - continue - } - value := values[0] - if encoding.ContentType != "" { - if strings.HasPrefix(encoding.ContentType, jsonContentType) { - if err := json.Unmarshal([]byte(value), ptr); err != nil { - return err - } - } - return errors.New("unsupported encoding, only application/json is supported") - } else { - var explode bool - if encoding.Explode != nil { - explode = *encoding.Explode - } - if err := BindStyledParameterWithLocation(encoding.Style, explode, tag, ParamLocationUndefined, value, field.Addr().Interface()); err != nil { - return err - } - } - } else { - // regular form data - if _, err := bindFormImpl(field, form, files, tag); err != nil { - return err - } - } - } - - return nil -} - -func MarshalForm(ptr interface{}, encodings map[string]RequestBodyEncoding) (url.Values, error) { - ptrVal := reflect.Indirect(reflect.ValueOf(ptr)) - if ptrVal.Kind() != reflect.Struct { - return nil, errors.New("form data body should be a struct") - } - tValue := ptrVal.Type() - result := make(url.Values) - for i := 0; i < tValue.NumField(); i++ { - field := ptrVal.Field(i) - tag := tValue.Field(i).Tag.Get(tagName) - if !field.CanInterface() || tag == "-" { - continue - } - omitEmpty := strings.HasSuffix(tag, ",omitempty") - if omitEmpty && field.IsZero() { - continue - } - tag = strings.Split(tag, ",")[0] // extract the name of the tag - if encoding, ok := encodings[tag]; ok && encoding.ContentType != "" { - if strings.HasPrefix(encoding.ContentType, jsonContentType) { - if data, err := json.Marshal(field); err != nil { //nolint:staticcheck - return nil, err - } else { - result[tag] = append(result[tag], string(data)) - } - } - return nil, errors.New("unsupported encoding, only application/json is supported") - } else { - marshalFormImpl(field, result, tag) - } - } - return result, nil -} - -func bindFormImpl(v reflect.Value, form map[string][]string, files map[string][]*multipart.FileHeader, name string) (bool, error) { - var hasData bool - switch v.Kind() { - case reflect.Interface: - return bindFormImpl(v.Elem(), form, files, name) - case reflect.Ptr: - ptrData := v.Elem() - if !ptrData.IsValid() { - ptrData = reflect.New(v.Type().Elem()) - } - ptrHasData, err := bindFormImpl(ptrData, form, files, name) - if err == nil && ptrHasData && !v.Elem().IsValid() { - v.Set(ptrData) - } - return ptrHasData, err - case reflect.Slice: - if files := append(files[name], files[name+"[]"]...); len(files) != 0 { - if _, ok := v.Interface().([]types.File); ok { - result := make([]types.File, len(files)) - for i, file := range files { - result[i].InitFromMultipart(file) - } - v.Set(reflect.ValueOf(result)) - hasData = true - } - } - indexedElementsCount := indexedElementsCount(form, files, name) - items := append(form[name], form[name+"[]"]...) - if indexedElementsCount+len(items) != 0 { - result := reflect.MakeSlice(v.Type(), indexedElementsCount+len(items), indexedElementsCount+len(items)) - for i := 0; i < indexedElementsCount; i++ { - if _, err := bindFormImpl(result.Index(i), form, files, fmt.Sprintf("%s[%v]", name, i)); err != nil { - return false, err - } - } - for i, item := range items { - if err := BindStringToObject(item, result.Index(indexedElementsCount+i).Addr().Interface()); err != nil { - return false, err - } - } - v.Set(result) - hasData = true - } - case reflect.Struct: - if files := files[name]; len(files) != 0 { - if file, ok := v.Interface().(types.File); ok { - file.InitFromMultipart(files[0]) - v.Set(reflect.ValueOf(file)) - return true, nil - } - } - for i := 0; i < v.NumField(); i++ { - field := v.Type().Field(i) - tag := field.Tag.Get(tagName) - if field.Name == "AdditionalProperties" && field.Type.Kind() == reflect.Map && tag == "-" { - additionalPropertiesHasData, err := bindAdditionalProperties(v.Field(i), v, form, files, name) - if err != nil { - return false, err - } - hasData = hasData || additionalPropertiesHasData - } - if !v.Field(i).CanInterface() || tag == "-" { - continue - } - tag = strings.Split(tag, ",")[0] // extract the name of the tag - fieldHasData, err := bindFormImpl(v.Field(i), form, files, fmt.Sprintf("%s[%s]", name, tag)) - if err != nil { - return false, err - } - hasData = hasData || fieldHasData - } - return hasData, nil - default: - value := form[name] - if len(value) != 0 { - return true, BindStringToObject(value[0], v.Addr().Interface()) - } - } - return hasData, nil -} - -func indexedElementsCount(form map[string][]string, files map[string][]*multipart.FileHeader, name string) int { - name += "[" - maxIndex := -1 - for k := range form { - if strings.HasPrefix(k, name) { - str := strings.TrimPrefix(k, name) - str = str[:strings.Index(str, "]")] - if idx, err := strconv.Atoi(str); err == nil { - if idx > maxIndex { - maxIndex = idx - } - } - } - } - for k := range files { - if strings.HasPrefix(k, name) { - str := strings.TrimPrefix(k, name) - str = str[:strings.Index(str, "]")] - if idx, err := strconv.Atoi(str); err == nil { - if idx > maxIndex { - maxIndex = idx - } - } - } - } - return maxIndex + 1 -} - -func bindAdditionalProperties(additionalProperties reflect.Value, parentStruct reflect.Value, form map[string][]string, files map[string][]*multipart.FileHeader, name string) (bool, error) { - hasData := false - valueType := additionalProperties.Type().Elem() - - // store all fixed properties in a set - fieldsSet := make(map[string]struct{}) - for i := 0; i < parentStruct.NumField(); i++ { - tag := parentStruct.Type().Field(i).Tag.Get(tagName) - if !parentStruct.Field(i).CanInterface() || tag == "-" { - continue - } - tag = strings.Split(tag, ",")[0] - fieldsSet[tag] = struct{}{} - } - - result := reflect.MakeMap(additionalProperties.Type()) - for k := range form { - if strings.HasPrefix(k, name+"[") { - key := strings.TrimPrefix(k, name+"[") - key = key[:strings.Index(key, "]")] - if _, ok := fieldsSet[key]; ok { - continue - } - value := reflect.New(valueType) - ptrHasData, err := bindFormImpl(value, form, files, fmt.Sprintf("%s[%s]", name, key)) - if err != nil { - return false, err - } - result.SetMapIndex(reflect.ValueOf(key), value.Elem()) - hasData = hasData || ptrHasData - } - } - for k := range files { - if strings.HasPrefix(k, name+"[") { - key := strings.TrimPrefix(k, name+"[") - key = key[:strings.Index(key, "]")] - if _, ok := fieldsSet[key]; ok { - continue - } - value := reflect.New(valueType) - result.SetMapIndex(reflect.ValueOf(key), value) - ptrHasData, err := bindFormImpl(value, form, files, fmt.Sprintf("%s[%s]", name, key)) - if err != nil { - return false, err - } - result.SetMapIndex(reflect.ValueOf(key), value.Elem()) - hasData = hasData || ptrHasData - } - } - if hasData { - additionalProperties.Set(result) - } - return hasData, nil -} - -func marshalFormImpl(v reflect.Value, result url.Values, name string) { - switch v.Kind() { - case reflect.Interface, reflect.Ptr: - marshalFormImpl(v.Elem(), result, name) - case reflect.Slice: - for i := 0; i < v.Len(); i++ { - elem := v.Index(i) - marshalFormImpl(elem, result, fmt.Sprintf("%s[%v]", name, i)) - } - case reflect.Struct: - for i := 0; i < v.NumField(); i++ { - field := v.Type().Field(i) - tag := field.Tag.Get(tagName) - if field.Name == "AdditionalProperties" && tag == "-" { - iter := v.MapRange() - for iter.Next() { - marshalFormImpl(iter.Value(), result, fmt.Sprintf("%s[%s]", name, iter.Key().String())) - } - continue - } - if !v.Field(i).CanInterface() || tag == "-" { - continue - } - tag = strings.Split(tag, ",")[0] // extract the name of the tag - marshalFormImpl(v.Field(i), result, fmt.Sprintf("%s[%s]", name, tag)) - } - default: - result[name] = append(result[name], fmt.Sprint(v.Interface())) - } -} diff --git a/pkg/runtime/bindform_test.go b/pkg/runtime/bindform_test.go deleted file mode 100644 index 159cef2c1f..0000000000 --- a/pkg/runtime/bindform_test.go +++ /dev/null @@ -1,195 +0,0 @@ -package runtime - -import ( - "bytes" - "mime/multipart" - "net/url" - "testing" - - "github.com/deepmap/oapi-codegen/pkg/types" - "github.com/stretchr/testify/assert" -) - -func TestBindURLForm(t *testing.T) { - type testSubStruct struct { - Int int `json:"int"` - String string `json:"string"` - AdditionalProperties map[string]string `json:"-"` - } - type testStruct struct { - Int int `json:"int"` - Bool bool `json:"bool,omitempty"` - String string `json:"string"` - IntSlice []int `json:"int_slice"` - Struct testSubStruct `json:"struct"` - StructSlice []testSubStruct `json:"struct_slice"` - OptInt *int `json:"opt_int,omitempty"` - OptBool *bool `json:"opt_bool,omitempty"` - OptString *string `json:"opt_string,omitempty"` - OptStruct *testSubStruct `json:"opt_struct,omitempty"` - OptStructSlice *[]testSubStruct `json:"opt_struct_slice,omitempty"` - NotSerializable int `json:"-"` - unexported int //nolint:unused - } - - testCases := map[string]testStruct{ - "int=123": {Int: 123}, - "bool=true": {Bool: true}, - "string=example": {String: "example"}, - "int_slice=1&int_slice=2&int_slice=3": {IntSlice: []int{1, 2, 3}}, - "int_slice[]=1&int_slice[]=2&int_slice[]=3": {IntSlice: []int{1, 2, 3}}, - "int_slice[2]=3&int_slice[1]=2&int_slice[0]=1": {IntSlice: []int{1, 2, 3}}, - "struct[int]=789&struct[string]=abc": {Struct: testSubStruct{Int: 789, String: "abc"}}, - "struct_slice[0][int]=3&struct_slice[0][string]=a&struct_slice[1][int]=2&struct_slice[1][string]=b&struct_slice[2][int]=1&struct_slice[2][string]=c": { - StructSlice: []testSubStruct{{Int: 3, String: "a"}, {Int: 2, String: "b"}, {Int: 1, String: "c"}}, - }, - "opt_int=456": {OptInt: func(v int) *int { return &v }(456)}, - "opt_bool=true": {OptBool: func(v bool) *bool { return &v }(true)}, - "opt_string=def": {OptString: func(v string) *string { return &v }("def")}, - "opt_struct[int]=456&opt_struct[string]=def": {OptStruct: &testSubStruct{Int: 456, String: "def"}}, - "opt_struct_slice[0][int]=123&opt_struct_slice[0][string]=abc&opt_struct_slice[1][int]=456&opt_struct_slice[1][string]=def": { - OptStructSlice: &([]testSubStruct{{Int: 123, String: "abc"}, {Int: 456, String: "def"}}), - }, - "opt_struct[additional_property]=123": { - OptStruct: &testSubStruct{AdditionalProperties: map[string]string{"additional_property": "123"}}, - }, - } - - for k, v := range testCases { - values, err := url.ParseQuery(k) - assert.NoError(t, err) - var result testStruct - err = BindForm(&result, values, nil, nil) - assert.NoError(t, err) - assert.Equal(t, v, result) - } -} - -func TestBindMultipartForm(t *testing.T) { - var testStruct struct { - File types.File `json:"file"` - OptFile *types.File `json:"opt_file,omitempty"` - Files []types.File `json:"files"` - OptFiles *[]types.File `json:"opt_files"` - } - - form, err := makeMultipartFilesForm([]fileData{{field: "file", filename: "123.txt", content: []byte("123")}}) - assert.NoError(t, err) - err = BindForm(&testStruct, form.Value, form.File, nil) - assert.NoError(t, err) - assert.Equal(t, "123.txt", testStruct.File.Filename()) - content, err := testStruct.File.Bytes() - assert.NoError(t, err) - assert.Equal(t, []byte("123"), content) - - form, err = makeMultipartFilesForm([]fileData{ - {field: "files", filename: "123.pdf", content: []byte("123")}, - {field: "files", filename: "456.pdf", content: []byte("456")}, - {field: "files", filename: "789.pdf", content: []byte("789")}, - }) - assert.NoError(t, err) - err = BindForm(&testStruct, form.Value, form.File, nil) - assert.NoError(t, err) - assert.Equal(t, 3, len(testStruct.Files)) - assert.Equal(t, "123.pdf", testStruct.Files[0].Filename()) - assert.Equal(t, "456.pdf", testStruct.Files[1].Filename()) - assert.Equal(t, "789.pdf", testStruct.Files[2].Filename()) - - form, err = makeMultipartFilesForm([]fileData{{field: "opt_file", filename: "456.png", content: []byte("456")}}) - assert.NoError(t, err) - err = BindForm(&testStruct, form.Value, form.File, nil) - assert.NoError(t, err) - assert.Equal(t, "456.png", testStruct.OptFile.Filename()) - content, err = testStruct.OptFile.Bytes() - assert.NoError(t, err) - assert.Equal(t, []byte("456"), content) - - form, err = makeMultipartFilesForm([]fileData{ - {field: "opt_files[2]", filename: "123.pdf", content: []byte("123")}, - {field: "opt_files[1]", filename: "456.pdf", content: []byte("456")}, - {field: "opt_files[0]", filename: "789.pdf", content: []byte("789")}, - }) - assert.NoError(t, err) - err = BindForm(&testStruct, form.Value, form.File, nil) - assert.NoError(t, err) - assert.NotNil(t, testStruct.OptFiles) - assert.Equal(t, 3, len(*testStruct.OptFiles)) - assert.Equal(t, "789.pdf", (*testStruct.OptFiles)[0].Filename()) - assert.Equal(t, "456.pdf", (*testStruct.OptFiles)[1].Filename()) - assert.Equal(t, "123.pdf", (*testStruct.OptFiles)[2].Filename()) -} - -func TestMarshalForm(t *testing.T) { - type testSubStruct struct { - Int int `json:"int"` - String string `json:"string"` - } - type testStruct struct { - Int int `json:"int,omitempty"` - Bool bool `json:"bool,omitempty"` - String string `json:"string,omitempty"` - IntSlice []int `json:"int_slice,omitempty"` - Struct testSubStruct `json:"struct,omitempty"` - StructSlice []testSubStruct `json:"struct_slice,omitempty"` - OptInt *int `json:"opt_int,omitempty"` - OptBool *bool `json:"opt_bool,omitempty"` - OptString *string `json:"opt_string,omitempty"` - OptStruct *testSubStruct `json:"opt_struct,omitempty"` - OptStructSlice *[]testSubStruct `json:"opt_struct_slice,omitempty"` - NotSerializable int `json:"-"` - unexported int //nolint:unused - } - - testCases := map[string]testStruct{ - "int=123": {Int: 123}, - "bool=true": {Bool: true}, - "string=example": {String: "example"}, - "int_slice[0]=1&int_slice[1]=2&int_slice[2]=3": {IntSlice: []int{1, 2, 3}}, - "struct[int]=789&struct[string]=abc": {Struct: testSubStruct{Int: 789, String: "abc"}}, - "struct_slice[0][int]=3&struct_slice[0][string]=a&struct_slice[1][int]=2&struct_slice[1][string]=b&struct_slice[2][int]=1&struct_slice[2][string]=c": { - StructSlice: []testSubStruct{{Int: 3, String: "a"}, {Int: 2, String: "b"}, {Int: 1, String: "c"}}, - }, - "opt_int=456": {OptInt: func(v int) *int { return &v }(456)}, - "opt_bool=true": {OptBool: func(v bool) *bool { return &v }(true)}, - "opt_string=def": {OptString: func(v string) *string { return &v }("def")}, - "opt_struct[int]=456&opt_struct[string]=def": {OptStruct: &testSubStruct{Int: 456, String: "def"}}, - "opt_struct_slice[0][int]=123&opt_struct_slice[0][string]=abc&opt_struct_slice[1][int]=456&opt_struct_slice[1][string]=def": { - OptStructSlice: &([]testSubStruct{{Int: 123, String: "abc"}, {Int: 456, String: "def"}}), - }, - } - - for k, v := range testCases { - marshalled, err := MarshalForm(v, nil) - assert.NoError(t, err) - encoded, err := url.QueryUnescape(marshalled.Encode()) - assert.NoError(t, err) - assert.Equal(t, k, encoded) - } -} - -type fileData struct { - field string - filename string - content []byte -} - -func makeMultipartFilesForm(files []fileData) (*multipart.Form, error) { - var buffer bytes.Buffer - mw := multipart.NewWriter(&buffer) - for _, file := range files { - w, err := mw.CreateFormFile(file.field, file.filename) - if err != nil { - return nil, err - } - _, err = w.Write(file.content) - if err != nil { - return nil, err - } - } - err := mw.Close() - if err != nil { - return nil, err - } - mr := multipart.NewReader(&buffer, mw.Boundary()) - return mr.ReadForm(1024) -} diff --git a/pkg/runtime/bindparam.go b/pkg/runtime/bindparam.go deleted file mode 100644 index 4fc1b30d3d..0000000000 --- a/pkg/runtime/bindparam.go +++ /dev/null @@ -1,526 +0,0 @@ -// Copyright 2019 DeepMap, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package runtime - -import ( - "encoding" - "encoding/json" - "errors" - "fmt" - "net/url" - "reflect" - "strings" - "time" - - "github.com/deepmap/oapi-codegen/pkg/types" -) - -// BindStyledParameter binds a parameter as described in the Path Parameters -// section here to a Go object: -// https://swagger.io/docs/specification/serialization/ -// It is a backward compatible function to clients generated with codegen -// up to version v1.5.5. v1.5.6+ calls the function below. -func BindStyledParameter(style string, explode bool, paramName string, - value string, dest interface{}) error { - return BindStyledParameterWithLocation(style, explode, paramName, ParamLocationUndefined, value, dest) -} - -// BindStyledParameterWithLocation binds a parameter as described in the Path Parameters -// section here to a Go object: -// https://swagger.io/docs/specification/serialization/ -func BindStyledParameterWithLocation(style string, explode bool, paramName string, - paramLocation ParamLocation, value string, dest interface{}) error { - - if value == "" { - return fmt.Errorf("parameter '%s' is empty, can't bind its value", paramName) - } - - // Based on the location of the parameter, we need to unescape it properly. - var err error - switch paramLocation { - case ParamLocationQuery, ParamLocationUndefined: - // We unescape undefined parameter locations here for older generated code, - // since prior to this refactoring, they always query unescaped. - value, err = url.QueryUnescape(value) - if err != nil { - return fmt.Errorf("error unescaping query parameter '%s': %v", paramName, err) - } - case ParamLocationPath: - value, err = url.PathUnescape(value) - if err != nil { - return fmt.Errorf("error unescaping path parameter '%s': %v", paramName, err) - } - default: - // Headers and cookies aren't escaped. - } - - // If the destination implements encoding.TextUnmarshaler we use it for binding - if tu, ok := dest.(encoding.TextUnmarshaler); ok { - if err := tu.UnmarshalText([]byte(value)); err != nil { - return fmt.Errorf("error unmarshalling '%s' text as %T: %s", value, dest, err) - } - - return nil - } - - // Everything comes in by pointer, dereference it - v := reflect.Indirect(reflect.ValueOf(dest)) - - // This is the basic type of the destination object. - t := v.Type() - - if t.Kind() == reflect.Struct { - // We've got a destination object, we'll create a JSON representation - // of the input value, and let the json library deal with the unmarshalling - parts, err := splitStyledParameter(style, explode, true, paramName, value) - if err != nil { - return err - } - - return bindSplitPartsToDestinationStruct(paramName, parts, explode, dest) - } - - if t.Kind() == reflect.Slice { - // Chop up the parameter into parts based on its style - parts, err := splitStyledParameter(style, explode, false, paramName, value) - if err != nil { - return fmt.Errorf("error splitting input '%s' into parts: %s", value, err) - } - - return bindSplitPartsToDestinationArray(parts, dest) - } - - // Try to bind the remaining types as a base type. - return BindStringToObject(value, dest) -} - -// This is a complex set of operations, but each given parameter style can be -// packed together in multiple ways, using different styles of separators, and -// different packing strategies based on the explode flag. This function takes -// as input any parameter format, and unpacks it to a simple list of strings -// or key-values which we can then treat generically. -// Why, oh why, great Swagger gods, did you have to make this so complicated? -func splitStyledParameter(style string, explode bool, object bool, paramName string, value string) ([]string, error) { - switch style { - case "simple": - // In the simple case, we always split on comma - parts := strings.Split(value, ",") - return parts, nil - case "label": - // In the label case, it's more tricky. In the no explode case, we have - // /users/.3,4,5 for arrays - // /users/.role,admin,firstName,Alex for objects - // in the explode case, we have: - // /users/.3.4.5 - // /users/.role=admin.firstName=Alex - if explode { - // In the exploded case, split everything on periods. - parts := strings.Split(value, ".") - // The first part should be an empty string because we have a - // leading period. - if parts[0] != "" { - return nil, fmt.Errorf("invalid format for label parameter '%s', should start with '.'", paramName) - } - return parts[1:], nil - - } else { - // In the unexploded case, we strip off the leading period. - if value[0] != '.' { - return nil, fmt.Errorf("invalid format for label parameter '%s', should start with '.'", paramName) - } - // The rest is comma separated. - return strings.Split(value[1:], ","), nil - } - - case "matrix": - if explode { - // In the exploded case, we break everything up on semicolon - parts := strings.Split(value, ";") - // The first part should always be empty string, since we started - // with ;something - if parts[0] != "" { - return nil, fmt.Errorf("invalid format for matrix parameter '%s', should start with ';'", paramName) - } - parts = parts[1:] - // Now, if we have an object, we just have a list of x=y statements. - // for a non-object, like an array, we have id=x, id=y. id=z, etc, - // so we need to strip the prefix from each of them. - if !object { - prefix := paramName + "=" - for i := range parts { - parts[i] = strings.TrimPrefix(parts[i], prefix) - } - } - return parts, nil - } else { - // In the unexploded case, parameters will start with ;paramName= - prefix := ";" + paramName + "=" - if !strings.HasPrefix(value, prefix) { - return nil, fmt.Errorf("expected parameter '%s' to start with %s", paramName, prefix) - } - str := strings.TrimPrefix(value, prefix) - return strings.Split(str, ","), nil - } - case "form": - var parts []string - if explode { - parts = strings.Split(value, "&") - if !object { - prefix := paramName + "=" - for i := range parts { - parts[i] = strings.TrimPrefix(parts[i], prefix) - } - } - return parts, nil - } else { - parts = strings.Split(value, ",") - prefix := paramName + "=" - for i := range parts { - parts[i] = strings.TrimPrefix(parts[i], prefix) - } - } - return parts, nil - } - - return nil, fmt.Errorf("unhandled parameter style: %s", style) -} - -// Given a set of values as a slice, create a slice to hold them all, and -// assign to each one by one. -func bindSplitPartsToDestinationArray(parts []string, dest interface{}) error { - // Everything comes in by pointer, dereference it - v := reflect.Indirect(reflect.ValueOf(dest)) - - // This is the basic type of the destination object. - t := v.Type() - - // We've got a destination array, bind each object one by one. - // This generates a slice of the correct element type and length to - // hold all the parts. - newArray := reflect.MakeSlice(t, len(parts), len(parts)) - for i, p := range parts { - err := BindStringToObject(p, newArray.Index(i).Addr().Interface()) - if err != nil { - return fmt.Errorf("error setting array element: %s", err) - } - } - v.Set(newArray) - return nil -} - -// Given a set of chopped up parameter parts, bind them to a destination -// struct. The exploded parameter controls whether we send key value pairs -// in the exploded case, or a sequence of values which are interpreted as -// tuples. -// Given the struct Id { firstName string, role string }, as in the canonical -// swagger examples, in the exploded case, we would pass -// ["firstName=Alex", "role=admin"], where in the non-exploded case, we would -// pass "firstName", "Alex", "role", "admin"] -// -// We punt the hard work of binding these values to the object to the json -// library. We'll turn those arrays into JSON strings, and unmarshal -// into the struct. -func bindSplitPartsToDestinationStruct(paramName string, parts []string, explode bool, dest interface{}) error { - // We've got a destination object, we'll create a JSON representation - // of the input value, and let the json library deal with the unmarshalling - var fields []string - if explode { - fields = make([]string, len(parts)) - for i, property := range parts { - propertyParts := strings.Split(property, "=") - if len(propertyParts) != 2 { - return fmt.Errorf("parameter '%s' has invalid exploded format", paramName) - } - fields[i] = "\"" + propertyParts[0] + "\":\"" + propertyParts[1] + "\"" - } - } else { - if len(parts)%2 != 0 { - return fmt.Errorf("parameter '%s' has invalid format, property/values need to be pairs", paramName) - } - fields = make([]string, len(parts)/2) - for i := 0; i < len(parts); i += 2 { - key := parts[i] - value := parts[i+1] - fields[i/2] = "\"" + key + "\":\"" + value + "\"" - } - } - jsonParam := "{" + strings.Join(fields, ",") + "}" - err := json.Unmarshal([]byte(jsonParam), dest) - if err != nil { - return fmt.Errorf("error binding parameter %s fields: %s", paramName, err) - } - return nil -} - -// BindQueryParameter works much like BindStyledParameter, however it takes a query argument -// input array from the url package, since query arguments come through a -// different path than the styled arguments. They're also exceptionally fussy. -// For example, consider the exploded and unexploded form parameter examples: -// (exploded) /users?role=admin&firstName=Alex -// (unexploded) /users?id=role,admin,firstName,Alex -// -// In the first case, we can pull the "id" parameter off the context, -// and unmarshal via json as an intermediate. Easy. In the second case, we -// don't have the id QueryParam present, but must find "role", and "firstName". -// what if there is another parameter similar to "ID" named "role"? We can't -// tell them apart. This code tries to fail, but the moral of the story is that -// you shouldn't pass objects via form styled query arguments, just use -// the Content parameter form. -func BindQueryParameter(style string, explode bool, required bool, paramName string, - queryParams url.Values, dest interface{}) error { - - // dv = destination value. - dv := reflect.Indirect(reflect.ValueOf(dest)) - - // intermediate value form which is either dv or dv dereferenced. - v := dv - - // inner code will bind the string's value to this interface. - var output interface{} - - if required { - // If the parameter is required, then the generated code will pass us - // a pointer to it: &int, &object, and so forth. We can directly set - // them. - output = dest - } else { - // For optional parameters, we have an extra indirect. An optional - // parameter of type "int" will be *int on the struct. We pass that - // in by pointer, and have **int. - - // If the destination, is a nil pointer, we need to allocate it. - if v.IsNil() { - t := v.Type() - newValue := reflect.New(t.Elem()) - // for now, hang onto the output buffer separately from destination, - // as we don't want to write anything to destination until we can - // unmarshal successfully, and check whether a field is required. - output = newValue.Interface() - } else { - // If the destination isn't nil, just use that. - output = v.Interface() - } - - // Get rid of that extra indirect as compared to the required case, - // so the code below doesn't have to care. - v = reflect.Indirect(reflect.ValueOf(output)) - } - - // This is the basic type of the destination object. - t := v.Type() - k := t.Kind() - - switch style { - case "form": - var parts []string - if explode { - // ok, the explode case in query arguments is very, very annoying, - // because an exploded object, such as /users?role=admin&firstName=Alex - // isn't actually present in the parameter array. We have to do - // different things based on destination type. - values, found := queryParams[paramName] - var err error - - switch k { - case reflect.Slice: - // In the slice case, we simply use the arguments provided by - // http library. - - if !found { - if required { - return fmt.Errorf("query parameter '%s' is required", paramName) - } else { - // If an optional parameter is not found, we do nothing, - return nil - } - } - err = bindSplitPartsToDestinationArray(values, output) - case reflect.Struct: - // This case is really annoying, and error prone, but the - // form style object binding doesn't tell us which arguments - // in the query string correspond to the object's fields. We'll - // try to bind field by field. - var fieldsPresent bool - fieldsPresent, err = bindParamsToExplodedObject(paramName, queryParams, output) - // If no fields were set, and there is no error, we will not fall - // through to assign the destination. - if !fieldsPresent { - return nil - } - default: - // Primitive object case. We expect to have 1 value to - // unmarshal. - if len(values) == 0 { - if required { - return fmt.Errorf("query parameter '%s' is required", paramName) - } else { - return nil - } - } - if len(values) != 1 { - return fmt.Errorf("multiple values for single value parameter '%s'", paramName) - } - - if !found { - if required { - return fmt.Errorf("query parameter '%s' is required", paramName) - } else { - // If an optional parameter is not found, we do nothing, - return nil - } - } - err = BindStringToObject(values[0], output) - } - if err != nil { - return err - } - // If the parameter is required, and we've successfully unmarshaled - // it, this assigns the new object to the pointer pointer. - if !required { - dv.Set(reflect.ValueOf(output)) - } - return nil - } else { - values, found := queryParams[paramName] - if !found { - if required { - return fmt.Errorf("query parameter '%s' is required", paramName) - } else { - return nil - } - } - if len(values) != 1 { - return fmt.Errorf("parameter '%s' is not exploded, but is specified multiple times", paramName) - } - parts = strings.Split(values[0], ",") - } - var err error - switch k { - case reflect.Slice: - err = bindSplitPartsToDestinationArray(parts, output) - case reflect.Struct: - err = bindSplitPartsToDestinationStruct(paramName, parts, explode, output) - default: - if len(parts) == 0 { - if required { - return fmt.Errorf("query parameter '%s' is required", paramName) - } else { - return nil - } - } - if len(parts) != 1 { - return fmt.Errorf("multiple values for single value parameter '%s'", paramName) - } - err = BindStringToObject(parts[0], output) - } - if err != nil { - return err - } - if !required { - dv.Set(reflect.ValueOf(output)) - } - return nil - case "deepObject": - if !explode { - return errors.New("deepObjects must be exploded") - } - return UnmarshalDeepObject(dest, paramName, queryParams) - case "spaceDelimited", "pipeDelimited": - return fmt.Errorf("query arguments of style '%s' aren't yet supported", style) - default: - return fmt.Errorf("style '%s' on parameter '%s' is invalid", style, paramName) - - } -} - -// bindParamsToExplodedObject reflects the destination structure, and pulls the value for -// each settable field from the given parameters map. This is to deal with the -// exploded form styled object which may occupy any number of parameter names. -// We don't try to be smart here, if the field exists as a query argument, -// set its value. This function returns a boolean, telling us whether there was -// anything to bind. There will be nothing to bind if a parameter isn't found by name, -// or none of an exploded object's fields are present. -func bindParamsToExplodedObject(paramName string, values url.Values, dest interface{}) (bool, error) { - // Dereference pointers to their destination values - binder, v, t := indirect(dest) - if binder != nil { - _, found := values[paramName] - if !found { - return false, nil - } - return true, BindStringToObject(values.Get(paramName), dest) - } - if t.Kind() != reflect.Struct { - return false, fmt.Errorf("unmarshalling query arg '%s' into wrong type", paramName) - } - - fieldsPresent := false - for i := 0; i < t.NumField(); i++ { - fieldT := t.Field(i) - - // Skip unsettable fields, such as internal ones. - if !v.Field(i).CanSet() { - continue - } - - // Find the json annotation on the field, and use the json specified - // name if available, otherwise, just the field name. - tag := fieldT.Tag.Get("json") - fieldName := fieldT.Name - if tag != "" { - tagParts := strings.Split(tag, ",") - name := tagParts[0] - if name != "" { - fieldName = name - } - } - - // At this point, we look up field name in the parameter list. - fieldVal, found := values[fieldName] - if found { - if len(fieldVal) != 1 { - return false, fmt.Errorf("field '%s' specified multiple times for param '%s'", fieldName, paramName) - } - err := BindStringToObject(fieldVal[0], v.Field(i).Addr().Interface()) - if err != nil { - return false, fmt.Errorf("could not bind query arg '%s' to request object: %s'", paramName, err) - } - fieldsPresent = true - } - } - return fieldsPresent, nil -} - -// indirect -func indirect(dest interface{}) (interface{}, reflect.Value, reflect.Type) { - v := reflect.ValueOf(dest) - if v.Type().NumMethod() > 0 && v.CanInterface() { - if u, ok := v.Interface().(Binder); ok { - return u, reflect.Value{}, nil - } - } - v = reflect.Indirect(v) - t := v.Type() - // special handling for custom types which might look like an object. We - // don't want to use object binding on them, but rather treat them as - // primitive types. time.Time{} is a unique case since we can't add a Binder - // to it without changing the underlying generated code. - if t.ConvertibleTo(reflect.TypeOf(time.Time{})) { - return dest, reflect.Value{}, nil - } - if t.ConvertibleTo(reflect.TypeOf(types.Date{})) { - return dest, reflect.Value{}, nil - } - return nil, v, t -} diff --git a/pkg/runtime/bindparam_test.go b/pkg/runtime/bindparam_test.go deleted file mode 100644 index 2334c50a1a..0000000000 --- a/pkg/runtime/bindparam_test.go +++ /dev/null @@ -1,500 +0,0 @@ -// Copyright 2019 DeepMap, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package runtime - -import ( - "encoding/json" - "fmt" - "math/big" - "net/url" - "testing" - "time" - - "github.com/deepmap/oapi-codegen/pkg/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// MockBinder is just an independent version of Binder that has the Bind implemented -type MockBinder struct { - time.Time -} - -func (d *MockBinder) Bind(src string) error { - // Don't fail on empty string. - if src == "" { - return nil - } - parsedTime, err := time.Parse(types.DateFormat, src) - if err != nil { - return fmt.Errorf("error parsing '%s' as date: %s", src, err) - } - d.Time = parsedTime - return nil -} - -func (d MockBinder) MarshalJSON() ([]byte, error) { - return json.Marshal(d.Time.Format(types.DateFormat)) -} - -func (d *MockBinder) UnmarshalJSON(data []byte) error { - var dateStr string - err := json.Unmarshal(data, &dateStr) - if err != nil { - return err - } - parsed, err := time.Parse(types.DateFormat, dateStr) - if err != nil { - return err - } - d.Time = parsed - return nil -} - -// EmbeddedMockBinder has an embedded MockBinder and so keeps the Binder Method from MockBinder. -type EmbeddedMockBinder struct { - MockBinder -} - -// AnotherMockBinder is an entirely new type we have to create a bind method with it to implement Binder as well. -type AnotherMockBinder MockBinder - -func (b *AnotherMockBinder) Bind(src string) error { - // Don't fail on empty string. - if src == "" { - return nil - } - parsedTime, err := time.Parse(types.DateFormat, src) - if err != nil { - return fmt.Errorf("error parsing '%s' as date: %s", src, err) - } - b.Time = parsedTime - return nil -} - -func TestSplitParameter(t *testing.T) { - // Please see the parameter serialization docs to understand these test - // cases - - expectedPrimitive := []string{"5"} - expectedArray := []string{"3", "4", "5"} - expectedObject := []string{"role", "admin", "firstName", "Alex"} - expectedExplodedObject := []string{"role=admin", "firstName=Alex"} - - var result []string - var err error - // ------------------------ simple style --------------------------------- - result, err = splitStyledParameter("simple", - false, - false, - "id", - "5") - assert.NoError(t, err) - assert.EqualValues(t, expectedPrimitive, result) - - result, err = splitStyledParameter("simple", - false, - false, - "id", - "3,4,5") - assert.NoError(t, err) - assert.EqualValues(t, expectedArray, result) - - result, err = splitStyledParameter("simple", - false, - true, - "id", - "role,admin,firstName,Alex") - assert.NoError(t, err) - assert.EqualValues(t, expectedObject, result) - - result, err = splitStyledParameter("simple", - true, - false, - "id", - "5") - assert.NoError(t, err) - assert.EqualValues(t, expectedPrimitive, result) - - result, err = splitStyledParameter("simple", - true, - false, - "id", - "3,4,5") - assert.NoError(t, err) - assert.EqualValues(t, expectedArray, result) - - result, err = splitStyledParameter("simple", - true, - true, - "id", - "role=admin,firstName=Alex") - assert.NoError(t, err) - assert.EqualValues(t, expectedExplodedObject, result) - - // ------------------------ label style --------------------------------- - result, err = splitStyledParameter("label", - false, - false, - "id", - ".5") - assert.NoError(t, err) - assert.EqualValues(t, expectedPrimitive, result) - - result, err = splitStyledParameter("label", - false, - false, - "id", - ".3,4,5") - assert.NoError(t, err) - assert.EqualValues(t, expectedArray, result) - - result, err = splitStyledParameter("label", - false, - true, - "id", - ".role,admin,firstName,Alex") - assert.NoError(t, err) - assert.EqualValues(t, expectedObject, result) - - result, err = splitStyledParameter("label", - true, - false, - "id", - ".5") - assert.NoError(t, err) - assert.EqualValues(t, expectedPrimitive, result) - - result, err = splitStyledParameter("label", - true, - false, - "id", - ".3.4.5") - assert.NoError(t, err) - assert.EqualValues(t, expectedArray, result) - - result, err = splitStyledParameter("label", - true, - true, - "id", - ".role=admin.firstName=Alex") - assert.NoError(t, err) - assert.EqualValues(t, expectedExplodedObject, result) - - // ------------------------ matrix style --------------------------------- - result, err = splitStyledParameter("matrix", - false, - false, - "id", - ";id=5") - assert.NoError(t, err) - assert.EqualValues(t, expectedPrimitive, result) - - result, err = splitStyledParameter("matrix", - false, - false, - "id", - ";id=3,4,5") - assert.NoError(t, err) - assert.EqualValues(t, expectedArray, result) - - result, err = splitStyledParameter("matrix", - false, - true, - "id", - ";id=role,admin,firstName,Alex") - assert.NoError(t, err) - assert.EqualValues(t, expectedObject, result) - - result, err = splitStyledParameter("matrix", - true, - false, - "id", - ";id=5") - assert.NoError(t, err) - assert.EqualValues(t, expectedPrimitive, result) - - result, err = splitStyledParameter("matrix", - true, - false, - "id", - ";id=3;id=4;id=5") - assert.NoError(t, err) - assert.EqualValues(t, expectedArray, result) - - result, err = splitStyledParameter("matrix", - true, - true, - "id", - ";role=admin;firstName=Alex") - assert.NoError(t, err) - assert.EqualValues(t, expectedExplodedObject, result) - - // ------------------------ form style --------------------------------- - result, err = splitStyledParameter("form", - false, - false, - "id", - "id=5") - assert.NoError(t, err) - assert.EqualValues(t, expectedPrimitive, result) - - result, err = splitStyledParameter("form", - false, - false, - "id", - "id=3,4,5") - assert.NoError(t, err) - assert.EqualValues(t, expectedArray, result) - - result, err = splitStyledParameter("form", - false, - true, - "id", - "id=role,admin,firstName,Alex") - assert.NoError(t, err) - assert.EqualValues(t, expectedObject, result) - - result, err = splitStyledParameter("form", - true, - false, - "id", - "id=5") - assert.NoError(t, err) - assert.EqualValues(t, expectedPrimitive, result) - - result, err = splitStyledParameter("form", - true, - false, - "id", - "id=3&id=4&id=5") - assert.NoError(t, err) - assert.EqualValues(t, expectedArray, result) - - result, err = splitStyledParameter("form", - true, - true, - "id", - "role=admin&firstName=Alex") - assert.NoError(t, err) - assert.EqualValues(t, expectedExplodedObject, result) -} - -func TestBindQueryParameter(t *testing.T) { - t.Run("deepObject", func(t *testing.T) { - type ID struct { - FirstName *string `json:"firstName"` - LastName *string `json:"lastName"` - Role string `json:"role"` - Birthday *types.Date `json:"birthday"` - Married *MockBinder `json:"married"` - } - - expectedName := "Alex" - expectedDeepObject := &ID{ - FirstName: &expectedName, - Role: "admin", - Birthday: &types.Date{Time: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)}, - Married: &MockBinder{time.Date(2020, 2, 2, 0, 0, 0, 0, time.UTC)}, - } - - actual := new(ID) - paramName := "id" - queryParams := url.Values{ - "id[firstName]": {"Alex"}, - "id[role]": {"admin"}, - "foo": {"bar"}, - "id[birthday]": {"2020-01-01"}, - "id[married]": {"2020-02-02"}, - } - - err := BindQueryParameter("deepObject", true, false, paramName, queryParams, &actual) - assert.NoError(t, err) - assert.Equal(t, expectedDeepObject, actual) - }) - - t.Run("form", func(t *testing.T) { - expected := &MockBinder{Time: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)} - birthday := &MockBinder{} - queryParams := url.Values{ - "birthday": {"2020-01-01"}, - } - err := BindQueryParameter("form", true, false, "birthday", queryParams, &birthday) - assert.NoError(t, err) - assert.Equal(t, expected, birthday) - }) - - t.Run("optional", func(t *testing.T) { - queryParams := url.Values{ - "time": {"2020-12-09T16:09:53+00:00"}, - "number": {"100"}, - } - // An optional time will be a pointer to a time in a parameter object - var optionalTime *time.Time - err := BindQueryParameter("form", true, false, "notfound", queryParams, &optionalTime) - require.NoError(t, err) - assert.Nil(t, optionalTime) - - var optionalNumber *int - err = BindQueryParameter("form", true, false, "notfound", queryParams, &optionalNumber) - require.NoError(t, err) - assert.Nil(t, optionalNumber) - - // If we require values, we require errors when they're not present. - err = BindQueryParameter("form", true, true, "notfound", queryParams, &optionalTime) - assert.Error(t, err) - err = BindQueryParameter("form", true, true, "notfound", queryParams, &optionalNumber) - assert.Error(t, err) - - }) -} - -func TestBindParameterViaAlias(t *testing.T) { - // We don't need to check every parameter format type here, since the binding - // code is identical irrespective of parameter type, buy we do want to test - // a bunch of types. - type AString string - type Aint int - type Afloat float64 - type Atime = time.Time - type Adate = MockBinder - - type AliasTortureTest struct { - S AString `json:"s"` - Sp *AString `json:"sp,omitempty"` - I Aint `json:"i"` - Ip *Aint `json:"ip,omitempty"` - F Afloat `json:"f"` - Fp *Afloat `json:"fp,omitempty"` - T Atime `json:"t"` - Tp *Atime `json:"tp,omitempty"` - D Adate `json:"d"` - Dp *Adate `json:"dp,omitempty"` - } - - now := time.Now().UTC() - later := now.Add(time.Hour) - - queryParams := url.Values{ - "alias[s]": {"str"}, - "alias[sp]": {"strp"}, - "alias[i]": {"1"}, - "alias[ip]": {"2"}, - "alias[f]": {"3.5"}, - "alias[fp]": {"4.5"}, - "alias[t]": {now.Format(time.RFC3339Nano)}, - "alias[tp]": {later.Format(time.RFC3339Nano)}, - "alias[d]": {"2020-11-06"}, - "alias[dp]": {"2020-11-07"}, - } - - dst := new(AliasTortureTest) - - err := BindQueryParameter("deepObject", true, false, "alias", queryParams, &dst) - require.NoError(t, err) - - var sp AString = "strp" - var ip Aint = 2 - var fp Afloat = 4.5 - dp := Adate{Time: time.Date(2020, 11, 7, 0, 0, 0, 0, time.UTC)} - - expected := AliasTortureTest{ - S: "str", - Sp: &sp, - I: 1, - Ip: &ip, - F: 3.5, - Fp: &fp, - T: now, - Tp: &later, - D: Adate{Time: time.Date(2020, 11, 6, 0, 0, 0, 0, time.UTC)}, - Dp: &dp, - } - - // Compare field by field, makes errors easier to track. - assert.EqualValues(t, expected.S, dst.S) - assert.EqualValues(t, expected.Sp, dst.Sp) - assert.EqualValues(t, expected.I, dst.I) - assert.EqualValues(t, expected.Ip, dst.Ip) - assert.EqualValues(t, expected.F, dst.F) - assert.EqualValues(t, expected.Fp, dst.Fp) - assert.EqualValues(t, expected.T, dst.T) - assert.EqualValues(t, expected.Tp, dst.Tp) - assert.EqualValues(t, expected.D, dst.D) - assert.EqualValues(t, expected.Dp, dst.Dp) -} - -// bindParamsToExplodedObject has to special case some types. Make sure that -// these non-object types are handled correctly. The other parts of the functionality -// are tested via more generic code above. -func TestBindParamsToExplodedObject(t *testing.T) { - now := time.Now().UTC() - values := url.Values{ - "time": {now.Format(time.RFC3339Nano)}, - "date": {"2020-11-06"}, - } - - var dstTime time.Time - fieldsPresent, err := bindParamsToExplodedObject("time", values, &dstTime) - assert.NoError(t, err) - assert.True(t, fieldsPresent) - assert.EqualValues(t, now, dstTime) - - type AliasedTime time.Time - var aDstTime AliasedTime - fieldsPresent, err = bindParamsToExplodedObject("time", values, &aDstTime) - assert.NoError(t, err) - assert.True(t, fieldsPresent) - assert.EqualValues(t, now, aDstTime) - - expectedDate := MockBinder{Time: time.Date(2020, 11, 6, 0, 0, 0, 0, time.UTC)} - - var dstDate MockBinder - fieldsPresent, err = bindParamsToExplodedObject("date", values, &dstDate) - assert.NoError(t, err) - assert.True(t, fieldsPresent) - assert.EqualValues(t, expectedDate, dstDate) - - var eDstDate EmbeddedMockBinder - fieldsPresent, err = bindParamsToExplodedObject("date", values, &eDstDate) - assert.NoError(t, err) - assert.True(t, fieldsPresent) - assert.EqualValues(t, expectedDate, dstDate) - - var nTDstDate AnotherMockBinder - fieldsPresent, err = bindParamsToExplodedObject("date", values, &nTDstDate) - assert.NoError(t, err) - assert.True(t, fieldsPresent) - assert.EqualValues(t, expectedDate, nTDstDate) - - type ObjectWithOptional struct { - Time *time.Time `json:"time,omitempty"` - } - - var optDstTime ObjectWithOptional - fieldsPresent, err = bindParamsToExplodedObject("explodedObject", values, &optDstTime) - assert.NoError(t, err) - assert.True(t, fieldsPresent) - assert.EqualValues(t, &now, optDstTime.Time) -} - -func TestBindStyledParameterWithLocation(t *testing.T) { - expectedBig := big.NewInt(12345678910) - - var dstBigNumber big.Int - err := BindStyledParameterWithLocation("simple", false, "id", ParamLocationUndefined, - "12345678910", &dstBigNumber) - assert.NoError(t, err) - assert.Equal(t, *expectedBig, dstBigNumber) -} diff --git a/pkg/runtime/bindstring.go b/pkg/runtime/bindstring.go deleted file mode 100644 index 72f74dd1bf..0000000000 --- a/pkg/runtime/bindstring.go +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright 2019 DeepMap, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package runtime - -import ( - "encoding" - "errors" - "fmt" - "reflect" - "strconv" - "time" - - "github.com/deepmap/oapi-codegen/pkg/types" -) - -// BindStringToObject takes a string, and attempts to assign it to the destination -// interface via whatever type conversion is necessary. We have to do this -// via reflection instead of a much simpler type switch so that we can handle -// type aliases. This function was the easy way out, the better way, since we -// know the destination type each place that we use this, is to generate code -// to read each specific type. -func BindStringToObject(src string, dst interface{}) error { - var err error - - v := reflect.ValueOf(dst) - t := reflect.TypeOf(dst) - - // We need to dereference pointers - if t.Kind() == reflect.Ptr { - v = reflect.Indirect(v) - t = v.Type() - } - - // For some optioinal args - if t.Kind() == reflect.Ptr { - if v.IsNil() { - v.Set(reflect.New(t.Elem())) - } - - v = reflect.Indirect(v) - t = v.Type() - } - - // The resulting type must be settable. reflect will catch issues like - // passing the destination by value. - if !v.CanSet() { - return errors.New("destination is not settable") - } - - switch t.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - var val int64 - val, err = strconv.ParseInt(src, 10, 64) - if err == nil { - if v.OverflowInt(val) { - err = fmt.Errorf("value '%s' overflows destination of type: %s", src, t.Kind()) - } - if err == nil { - v.SetInt(val) - } - } - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - var val uint64 - val, err = strconv.ParseUint(src, 10, 64) - if err == nil { - if v.OverflowUint(val) { - err = fmt.Errorf("value '%s' overflows destination of type: %s", src, t.Kind()) - } - v.SetUint(val) - } - case reflect.String: - v.SetString(src) - err = nil - case reflect.Float64, reflect.Float32: - var val float64 - val, err = strconv.ParseFloat(src, 64) - if err == nil { - if v.OverflowFloat(val) { - err = fmt.Errorf("value '%s' overflows destination of type: %s", src, t.Kind()) - } - v.SetFloat(val) - } - case reflect.Bool: - var val bool - val, err = strconv.ParseBool(src) - if err == nil { - v.SetBool(val) - } - case reflect.Array: - if tu, ok := dst.(encoding.TextUnmarshaler); ok { - if err := tu.UnmarshalText([]byte(src)); err != nil { - return fmt.Errorf("error unmarshalling '%s' text as %T: %s", src, dst, err) - } - - return nil - } - fallthrough - case reflect.Struct: - // if this is not of type Time or of type Date look to see if this is of type Binder. - if dstType, ok := dst.(Binder); ok { - return dstType.Bind(src) - } - - if t.ConvertibleTo(reflect.TypeOf(time.Time{})) { - // Don't fail on empty string. - if src == "" { - return nil - } - // Time is a special case of a struct that we handle - parsedTime, err := time.Parse(time.RFC3339Nano, src) - if err != nil { - parsedTime, err = time.Parse(types.DateFormat, src) - if err != nil { - return fmt.Errorf("error parsing '%s' as RFC3339 or 2006-01-02 time: %s", src, err) - } - } - // So, assigning this gets a little fun. We have a value to the - // dereference destination. We can't do a conversion to - // time.Time because the result isn't assignable, so we need to - // convert pointers. - if t != reflect.TypeOf(time.Time{}) { - vPtr := v.Addr() - vtPtr := vPtr.Convert(reflect.TypeOf(&time.Time{})) - v = reflect.Indirect(vtPtr) - } - v.Set(reflect.ValueOf(parsedTime)) - return nil - } - - if t.ConvertibleTo(reflect.TypeOf(types.Date{})) { - // Don't fail on empty string. - if src == "" { - return nil - } - parsedTime, err := time.Parse(types.DateFormat, src) - if err != nil { - return fmt.Errorf("error parsing '%s' as date: %s", src, err) - } - parsedDate := types.Date{Time: parsedTime} - - // We have to do the same dance here to assign, just like with times - // above. - if t != reflect.TypeOf(types.Date{}) { - vPtr := v.Addr() - vtPtr := vPtr.Convert(reflect.TypeOf(&types.Date{})) - v = reflect.Indirect(vtPtr) - } - v.Set(reflect.ValueOf(parsedDate)) - return nil - } - - // We fall through to the error case below if we haven't handled the - // destination type above. - fallthrough - default: - // We've got a bunch of types unimplemented, don't fail silently. - err = fmt.Errorf("can not bind to destination of type: %s", t.Kind()) - } - if err != nil { - return fmt.Errorf("error binding string parameter: %s", err) - } - return nil -} diff --git a/pkg/runtime/bindstring_test.go b/pkg/runtime/bindstring_test.go deleted file mode 100644 index 0ce0914bde..0000000000 --- a/pkg/runtime/bindstring_test.go +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright 2019 DeepMap, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package runtime - -import ( - "fmt" - "math" - "testing" - "time" - - "github.com/deepmap/oapi-codegen/pkg/types" - "github.com/stretchr/testify/assert" -) - -func TestBindStringToObject(t *testing.T) { - var i int - assert.NoError(t, BindStringToObject("5", &i)) - assert.Equal(t, 5, i) - - // Let's make sure we error out on things that can't be the correct - // type. Since we're using reflect package setters, we'll have similar - // unassignable type errors. - assert.Error(t, BindStringToObject("5.7", &i)) - assert.Error(t, BindStringToObject("foo", &i)) - assert.Error(t, BindStringToObject("1,2,3", &i)) - - var i8 int8 - assert.NoError(t, BindStringToObject("12", &i8)) - assert.Equal(t, int8(12), i8) - assert.NoError(t, BindStringToObject("-12", &i8)) - assert.Equal(t, int8(-12), i8) - - assert.Error(t, BindStringToObject("5.7", &i8)) - assert.Error(t, BindStringToObject("foo", &i8)) - assert.Error(t, BindStringToObject("1,2,3", &i8)) - assert.Error(t, BindStringToObject(fmt.Sprintf("%d", math.MinInt8-1), &i8)) - assert.Error(t, BindStringToObject(fmt.Sprintf("%d", math.MaxInt8+1), &i8)) - - var i16 int16 - assert.NoError(t, BindStringToObject("12", &i16)) - assert.Equal(t, int16(12), i16) - assert.NoError(t, BindStringToObject("-12", &i16)) - assert.Equal(t, int16(-12), i16) - - assert.Error(t, BindStringToObject("5.7", &i16)) - assert.Error(t, BindStringToObject("foo", &i16)) - assert.Error(t, BindStringToObject("1,2,3", &i16)) - assert.Error(t, BindStringToObject(fmt.Sprintf("%d", math.MinInt16-1), &i16)) - assert.Error(t, BindStringToObject(fmt.Sprintf("%d", math.MaxInt16+1), &i16)) - - var i32 int32 - assert.NoError(t, BindStringToObject("12", &i32)) - assert.Equal(t, int32(12), i32) - assert.NoError(t, BindStringToObject("-12", &i32)) - assert.Equal(t, int32(-12), i32) - - assert.Error(t, BindStringToObject("5.7", &i32)) - assert.Error(t, BindStringToObject("foo", &i32)) - assert.Error(t, BindStringToObject("1,2,3", &i32)) - assert.Error(t, BindStringToObject(fmt.Sprintf("%d", math.MinInt32-1), &i32)) - assert.Error(t, BindStringToObject(fmt.Sprintf("%d", math.MaxInt32+1), &i32)) - - var i64 int64 - assert.NoError(t, BindStringToObject("124", &i64)) - assert.Equal(t, int64(124), i64) - assert.NoError(t, BindStringToObject("-124", &i64)) - assert.Equal(t, int64(-124), i64) - - assert.Error(t, BindStringToObject("5.7", &i64)) - assert.Error(t, BindStringToObject("foo", &i64)) - assert.Error(t, BindStringToObject("1,2,3", &i64)) - assert.Error(t, BindStringToObject("-9223372036854775809", &i64)) - assert.Error(t, BindStringToObject("9223372036854775808", &i64)) // 1<<63 - - var u uint - assert.NoError(t, BindStringToObject("5", &u)) - assert.Equal(t, uint(5), u) - - assert.Error(t, BindStringToObject("-5", &u)) - assert.Error(t, BindStringToObject("5.7", &u)) - assert.Error(t, BindStringToObject("foo", &u)) - assert.Error(t, BindStringToObject("1,2,3", &u)) - - var u8 uint8 - assert.NoError(t, BindStringToObject("12", &u8)) - assert.Equal(t, uint8(12), u8) - - assert.Error(t, BindStringToObject("-5", &u8)) - assert.Error(t, BindStringToObject("5.7", &u8)) - assert.Error(t, BindStringToObject("foo", &u8)) - assert.Error(t, BindStringToObject("1,2,3", &u8)) - assert.Error(t, BindStringToObject(fmt.Sprintf("%d", math.MaxUint8+1), &i8)) - - var u16 uint16 - assert.NoError(t, BindStringToObject("12", &u16)) - assert.Equal(t, uint16(12), u16) - - assert.Error(t, BindStringToObject("-5", &u16)) - assert.Error(t, BindStringToObject("5.7", &u16)) - assert.Error(t, BindStringToObject("foo", &u16)) - assert.Error(t, BindStringToObject("1,2,3", &u16)) - assert.Error(t, BindStringToObject(fmt.Sprintf("%d", math.MaxUint16+1), &i16)) - - var u32 uint32 - assert.NoError(t, BindStringToObject("12", &u32)) - assert.Equal(t, uint32(12), u32) - - assert.Error(t, BindStringToObject("-5", &u32)) - assert.Error(t, BindStringToObject("5.7", &u32)) - assert.Error(t, BindStringToObject("foo", &u32)) - assert.Error(t, BindStringToObject("1,2,3", &u32)) - assert.Error(t, BindStringToObject(fmt.Sprintf("%d", math.MaxUint32+1), &i32)) - - var u64 uint64 - assert.NoError(t, BindStringToObject("124", &u64)) - assert.Equal(t, uint64(124), u64) - - assert.Error(t, BindStringToObject("-5", &u64)) - assert.Error(t, BindStringToObject("5.7", &u64)) - assert.Error(t, BindStringToObject("foo", &u64)) - assert.Error(t, BindStringToObject("1,2,3", &u64)) - assert.Error(t, BindStringToObject("18446744073709551616", &i64)) // 1<<64 - - var b bool - assert.NoError(t, BindStringToObject("True", &b)) - assert.Equal(t, true, b) - assert.NoError(t, BindStringToObject("true", &b)) - assert.Equal(t, true, b) - assert.NoError(t, BindStringToObject("1", &b)) - assert.Equal(t, true, b) - assert.NoError(t, BindStringToObject("0", &b)) - assert.Equal(t, false, b) - assert.Error(t, BindStringToObject("-1", &b)) - assert.Error(t, BindStringToObject("hello", &b)) - - var f64 float64 - assert.NoError(t, BindStringToObject("1.25", &f64)) - assert.Equal(t, float64(1.25), f64) - - assert.Error(t, BindStringToObject("foo", &f64)) - assert.Error(t, BindStringToObject("1,2,3", &f64)) - - var f32 float32 - assert.NoError(t, BindStringToObject("3.125", &f32)) - assert.Equal(t, float32(3.125), f32) - - assert.Error(t, BindStringToObject("foo", &f32)) - assert.Error(t, BindStringToObject("1,2,3", &f32)) - assert.NoError(t, BindStringToObject(fmt.Sprintf("%f", math.MaxFloat32), &f32)) - assert.Error(t, BindStringToObject(fmt.Sprintf("%f", math.MaxFloat32*2), &f32)) - - // This checks whether binding works through a type alias. - type SomeType int - var st SomeType - assert.NoError(t, BindStringToObject("5", &st)) - assert.Equal(t, SomeType(5), st) - - // Check time binding - now := time.Now().UTC() - strTime := now.Format(time.RFC3339Nano) - var parsedTime time.Time - assert.NoError(t, BindStringToObject(strTime, &parsedTime)) - parsedTime = parsedTime.UTC() - assert.EqualValues(t, now, parsedTime) - - now = now.Truncate(time.Second) - strTime = now.Format(time.RFC3339) - assert.NoError(t, BindStringToObject(strTime, &parsedTime)) - parsedTime = parsedTime.UTC() - assert.EqualValues(t, now, parsedTime) - - // Checks whether time binding works through a type alias. - type AliasedTime time.Time - var aliasedTime AliasedTime - assert.NoError(t, BindStringToObject(strTime, &aliasedTime)) - assert.EqualValues(t, now, aliasedTime) - - // Checks whether date binding works directly and through an alias. - dateString := "2020-11-05" - var dstDate types.Date - assert.NoError(t, BindStringToObject(dateString, &dstDate)) - type AliasedDate types.Date - var dstAliasedDate AliasedDate - assert.NoError(t, BindStringToObject(dateString, &dstAliasedDate)) - - // Checks whether a mock binder works and embedded types - var mockBinder MockBinder - assert.NoError(t, BindStringToObject(dateString, &mockBinder)) - assert.EqualValues(t, dateString, mockBinder.Time.Format("2006-01-02")) - var dstEmbeddedMockBinder EmbeddedMockBinder - assert.NoError(t, BindStringToObject(dateString, &dstEmbeddedMockBinder)) - assert.EqualValues(t, dateString, dstEmbeddedMockBinder.Time.Format("2006-01-02")) - - // Checks UUID binding - uuidString := "bbca1470-5e1f-4c64-ba99-fa7a6d2687b0" - var dstUUID types.UUID - assert.NoError(t, BindStringToObject(uuidString, &dstUUID)) - assert.Equal(t, dstUUID.String(), uuidString) - -} diff --git a/pkg/runtime/deepobject.go b/pkg/runtime/deepobject.go deleted file mode 100644 index 87e9dd9a24..0000000000 --- a/pkg/runtime/deepobject.go +++ /dev/null @@ -1,358 +0,0 @@ -package runtime - -import ( - "encoding/json" - "errors" - "fmt" - "net/url" - "reflect" - "sort" - "strconv" - "strings" - "time" - - "github.com/deepmap/oapi-codegen/pkg/types" -) - -func marshalDeepObject(in interface{}, path []string) ([]string, error) { - var result []string - - switch t := in.(type) { - case []interface{}: - // For the array, we will use numerical subscripts of the form [x], - // in the same order as the array. - for i, iface := range t { - newPath := append(path, strconv.Itoa(i)) - fields, err := marshalDeepObject(iface, newPath) - if err != nil { - return nil, fmt.Errorf("error traversing array: %w", err) - } - result = append(result, fields...) - } - case map[string]interface{}: - // For a map, each key (field name) becomes a member of the path, and - // we recurse. First, sort the keys. - keys := make([]string, len(t)) - i := 0 - for k := range t { - keys[i] = k - i++ - } - sort.Strings(keys) - - // Now, for each key, we recursively marshal it. - for _, k := range keys { - newPath := append(path, k) - fields, err := marshalDeepObject(t[k], newPath) - if err != nil { - return nil, fmt.Errorf("error traversing map: %w", err) - } - result = append(result, fields...) - } - default: - // Now, for a concrete value, we will turn the path elements - // into a deepObject style set of subscripts. [a, b, c] turns into - // [a][b][c] - prefix := "[" + strings.Join(path, "][") + "]" - result = []string{ - prefix + fmt.Sprintf("=%v", t), - } - } - return result, nil -} - -func MarshalDeepObject(i interface{}, paramName string) (string, error) { - // We're going to marshal to JSON and unmarshal into an interface{}, - // which will use the json pkg to deal with all the field annotations. We - // can then walk the generic object structure to produce a deepObject. This - // isn't efficient and it would be more efficient to reflect on our own, - // but it's complicated, error-prone code. - buf, err := json.Marshal(i) - if err != nil { - return "", fmt.Errorf("failed to marshal input to JSON: %w", err) - } - var i2 interface{} - err = json.Unmarshal(buf, &i2) - if err != nil { - return "", fmt.Errorf("failed to unmarshal JSON: %w", err) - } - fields, err := marshalDeepObject(i2, nil) - if err != nil { - return "", fmt.Errorf("error traversing JSON structure: %w", err) - } - - // Prefix the param name to each subscripted field. - for i := range fields { - fields[i] = paramName + fields[i] - } - return strings.Join(fields, "&"), nil -} - -type fieldOrValue struct { - fields map[string]fieldOrValue - value string -} - -func (f *fieldOrValue) appendPathValue(path []string, value string) { - fieldName := path[0] - if len(path) == 1 { - f.fields[fieldName] = fieldOrValue{value: value} - return - } - - pv, found := f.fields[fieldName] - if !found { - pv = fieldOrValue{ - fields: make(map[string]fieldOrValue), - } - f.fields[fieldName] = pv - } - pv.appendPathValue(path[1:], value) -} - -func makeFieldOrValue(paths [][]string, values []string) fieldOrValue { - - f := fieldOrValue{ - fields: make(map[string]fieldOrValue), - } - for i := range paths { - path := paths[i] - value := values[i] - f.appendPathValue(path, value) - } - return f -} - -func UnmarshalDeepObject(dst interface{}, paramName string, params url.Values) error { - // Params are all the query args, so we need those that look like - // "paramName["... - var fieldNames []string - var fieldValues []string - searchStr := paramName + "[" - for pName, pValues := range params { - if strings.HasPrefix(pName, searchStr) { - // trim the parameter name from the full name. - pName = pName[len(paramName):] - fieldNames = append(fieldNames, pName) - if len(pValues) != 1 { - return fmt.Errorf("%s has multiple values", pName) - } - fieldValues = append(fieldValues, pValues[0]) - } - } - - // Now, for each field, reconstruct its subscript path and value - paths := make([][]string, len(fieldNames)) - for i, path := range fieldNames { - path = strings.TrimLeft(path, "[") - path = strings.TrimRight(path, "]") - paths[i] = strings.Split(path, "][") - } - - fieldPaths := makeFieldOrValue(paths, fieldValues) - err := assignPathValues(dst, fieldPaths) - if err != nil { - return fmt.Errorf("error assigning value to destination: %w", err) - } - - return nil -} - -// This returns a field name, either using the variable name, or the json -// annotation if that exists. -func getFieldName(f reflect.StructField) string { - n := f.Name - tag, found := f.Tag.Lookup("json") - if found { - // If we have a json field, and the first part of it before the - // first comma is non-empty, that's our field name. - parts := strings.Split(tag, ",") - if parts[0] != "" { - n = parts[0] - } - } - return n -} - -// Create a map of field names that we'll see in the deepObject to reflect -// field indices on the given type. -func fieldIndicesByJsonTag(i interface{}) (map[string]int, error) { - t := reflect.TypeOf(i) - if t.Kind() != reflect.Struct { - return nil, errors.New("expected a struct as input") - } - - n := t.NumField() - fieldMap := make(map[string]int) - for i := 0; i < n; i++ { - field := t.Field(i) - fieldName := getFieldName(field) - fieldMap[fieldName] = i - } - return fieldMap, nil -} - -func assignPathValues(dst interface{}, pathValues fieldOrValue) error { - //t := reflect.TypeOf(dst) - v := reflect.ValueOf(dst) - - iv := reflect.Indirect(v) - it := iv.Type() - - switch it.Kind() { - case reflect.Slice: - sliceLength := len(pathValues.fields) - dstSlice := reflect.MakeSlice(it, sliceLength, sliceLength) - err := assignSlice(dstSlice, pathValues) - if err != nil { - return fmt.Errorf("error assigning slice: %w", err) - } - iv.Set(dstSlice) - return nil - case reflect.Struct: - // Some special types we care about are structs. Handle them - // here. They may be redefined, so we need to do some hoop - // jumping. If the types are aliased, we need to type convert - // the pointer, then set the value of the dereference pointer. - - // We check to see if the object implements the Binder interface first. - if dst, isBinder := v.Interface().(Binder); isBinder { - return dst.Bind(pathValues.value) - } - // Then check the legacy types - if it.ConvertibleTo(reflect.TypeOf(types.Date{})) { - var date types.Date - var err error - date.Time, err = time.Parse(types.DateFormat, pathValues.value) - if err != nil { - return fmt.Errorf("invalid date format: %w", err) - } - dst := iv - if it != reflect.TypeOf(types.Date{}) { - // Types are aliased, convert the pointers. - ivPtr := iv.Addr() - aPtr := ivPtr.Convert(reflect.TypeOf(&types.Date{})) - dst = reflect.Indirect(aPtr) - } - dst.Set(reflect.ValueOf(date)) - } - if it.ConvertibleTo(reflect.TypeOf(time.Time{})) { - var tm time.Time - var err error - tm, err = time.Parse(time.RFC3339Nano, pathValues.value) - if err != nil { - // Fall back to parsing it as a date. - // TODO: why is this marked as an ineffassign? - tm, err = time.Parse(types.DateFormat, pathValues.value) //nolint:ineffassign,staticcheck - if err != nil { - return fmt.Errorf("error parsing '%s' as RFC3339 or 2006-01-02 time: %s", pathValues.value, err) - } - return fmt.Errorf("invalid date format: %w", err) - } - dst := iv - if it != reflect.TypeOf(time.Time{}) { - // Types are aliased, convert the pointers. - ivPtr := iv.Addr() - aPtr := ivPtr.Convert(reflect.TypeOf(&time.Time{})) - dst = reflect.Indirect(aPtr) - } - dst.Set(reflect.ValueOf(tm)) - } - fieldMap, err := fieldIndicesByJsonTag(iv.Interface()) - if err != nil { - return fmt.Errorf("failed enumerating fields: %w", err) - } - for _, fieldName := range sortedFieldOrValueKeys(pathValues.fields) { - fieldValue := pathValues.fields[fieldName] - fieldIndex, found := fieldMap[fieldName] - if !found { - return fmt.Errorf("field [%s] is not present in destination object", fieldName) - } - field := iv.Field(fieldIndex) - err = assignPathValues(field.Addr().Interface(), fieldValue) - if err != nil { - return fmt.Errorf("error assigning field [%s]: %w", fieldName, err) - } - } - return nil - case reflect.Ptr: - // If we have a pointer after redirecting, it means we're dealing with - // an optional field, such as *string, which was passed in as &foo. We - // will allocate it if necessary, and call ourselves with a different - // interface. - dstVal := reflect.New(it.Elem()) - dstPtr := dstVal.Interface() - err := assignPathValues(dstPtr, pathValues) - iv.Set(dstVal) - return err - case reflect.Bool: - val, err := strconv.ParseBool(pathValues.value) - if err != nil { - return fmt.Errorf("expected a valid bool, got %s", pathValues.value) - } - iv.SetBool(val) - return nil - case reflect.Float32: - val, err := strconv.ParseFloat(pathValues.value, 32) - if err != nil { - return fmt.Errorf("expected a valid float, got %s", pathValues.value) - } - iv.SetFloat(val) - return nil - case reflect.Float64: - val, err := strconv.ParseFloat(pathValues.value, 64) - if err != nil { - return fmt.Errorf("expected a valid float, got %s", pathValues.value) - } - iv.SetFloat(val) - return nil - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - val, err := strconv.ParseInt(pathValues.value, 10, 64) - if err != nil { - return fmt.Errorf("expected a valid int, got %s", pathValues.value) - } - iv.SetInt(val) - return nil - case reflect.String: - iv.SetString(pathValues.value) - return nil - default: - return errors.New("unhandled type: " + it.String()) - } -} - -func assignSlice(dst reflect.Value, pathValues fieldOrValue) error { - // Gather up the values - nValues := len(pathValues.fields) - values := make([]string, nValues) - // We expect to have consecutive array indices in the map - for i := 0; i < nValues; i++ { - indexStr := strconv.Itoa(i) - fv, found := pathValues.fields[indexStr] - if !found { - return errors.New("array deepObjects must have consecutive indices") - } - values[i] = fv.value - } - - // This could be cleaner, but we can call into assignPathValues to - // avoid recreating this logic. - for i := 0; i < nValues; i++ { - dstElem := dst.Index(i).Addr() - err := assignPathValues(dstElem.Interface(), fieldOrValue{value: values[i]}) - if err != nil { - return fmt.Errorf("error binding array: %w", err) - } - } - - return nil -} - -func sortedFieldOrValueKeys(m map[string]fieldOrValue) []string { - keys := make([]string, 0, len(m)) - for k := range m { - keys = append(keys, k) - } - sort.Strings(keys) - return keys -} diff --git a/pkg/runtime/deepobject_test.go b/pkg/runtime/deepobject_test.go deleted file mode 100644 index d1a5a16377..0000000000 --- a/pkg/runtime/deepobject_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package runtime - -import ( - "net/url" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type InnerObject struct { - Name string - ID int -} - -// These are all possible field types, mandatory and optional. -type AllFields struct { - I int `json:"i"` - Oi *int `json:"oi,omitempty"` - F float32 `json:"f"` - Of *float32 `json:"of,omitempty"` - B bool `json:"b"` - Ob *bool `json:"ob,omitempty"` - As []string `json:"as"` - Oas *[]string `json:"oas,omitempty"` - O InnerObject `json:"o"` - Oo *InnerObject `json:"oo,omitempty"` - D MockBinder `json:"d"` - Od *MockBinder `json:"od,omitempty"` -} - -func TestDeepObject(t *testing.T) { - oi := int(5) - of := float32(3.7) - ob := true - oas := []string{"foo", "bar"} - oo := InnerObject{ - Name: "Marcin Romaszewicz", - ID: 123, - } - d := MockBinder{Time: time.Date(2020, 2, 1, 0, 0, 0, 0, time.UTC)} - - srcObj := AllFields{ - I: 12, - Oi: &oi, - F: 4.2, - Of: &of, - B: true, - Ob: &ob, - As: []string{"hello", "world"}, - Oas: &oas, - O: InnerObject{ - Name: "Joe Schmoe", - ID: 456, - }, - Oo: &oo, - D: d, - Od: &d, - } - - marshaled, err := MarshalDeepObject(srcObj, "p") - require.NoError(t, err) - t.Log(marshaled) - - params := make(url.Values) - marshaledParts := strings.Split(marshaled, "&") - for _, p := range marshaledParts { - parts := strings.Split(p, "=") - require.Equal(t, 2, len(parts)) - params.Set(parts[0], parts[1]) - } - - var dstObj AllFields - err = UnmarshalDeepObject(&dstObj, "p", params) - require.NoError(t, err) - assert.EqualValues(t, srcObj, dstObj) -} diff --git a/pkg/runtime/jsonmerge.go b/pkg/runtime/jsonmerge.go deleted file mode 100644 index 155abfbeb6..0000000000 --- a/pkg/runtime/jsonmerge.go +++ /dev/null @@ -1,26 +0,0 @@ -package runtime - -import ( - "encoding/json" - - "github.com/apapsch/go-jsonmerge/v2" -) - -// JsonMerge merges two JSON representation into a single object. `data` is the -// existing representation and `patch` is the new data to be merged in -func JsonMerge(data, patch json.RawMessage) (json.RawMessage, error) { - merger := jsonmerge.Merger{ - CopyNonexistent: true, - } - if data == nil { - data = []byte(`{}`) - } - if patch == nil { - patch = []byte(`{}`) - } - merged, err := merger.MergeBytes(data, patch) - if err != nil { - return nil, err - } - return merged, nil -} diff --git a/pkg/runtime/jsonmerge_test.go b/pkg/runtime/jsonmerge_test.go deleted file mode 100644 index 8ec8dd7107..0000000000 --- a/pkg/runtime/jsonmerge_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package runtime - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestJsonMerge(t *testing.T) { - t.Run("when object", func(t *testing.T) { - t.Run("Merges properties defined in both objects", func(t *testing.T) { - data := `{"foo": 1}` - patch := `{"foo": null}` - expected := `{"foo":null}` - - actual, err := JsonMerge([]byte(data), []byte(patch)) - assert.NoError(t, err) - assert.Equal(t, expected, string(actual)) - }) - - t.Run("Sets property defined in only src object", func(t *testing.T) { - data := `{}` - patch := `{"source":"merge-me"}` - expected := `{"source":"merge-me"}` - - actual, err := JsonMerge([]byte(data), []byte(patch)) - assert.NoError(t, err) - assert.Equal(t, expected, string(actual)) - }) - - t.Run("Handles child objects", func(t *testing.T) { - data := `{"channel":{"status":"valid"}}` - patch := `{"channel":{"id":1}}` - expected := `{"channel":{"id":1,"status":"valid"}}` - - actual, err := JsonMerge([]byte(data), []byte(patch)) - assert.NoError(t, err) - assert.Equal(t, expected, string(actual)) - }) - - t.Run("Handles empty objects", func(t *testing.T) { - data := `{}` - patch := `{}` - expected := `{}` - - actual, err := JsonMerge([]byte(data), []byte(patch)) - assert.NoError(t, err) - assert.Equal(t, expected, string(actual)) - }) - - t.Run("Handles nil data", func(t *testing.T) { - patch := `{"foo":"bar"}` - expected := `{"foo":"bar"}` - - actual, err := JsonMerge(nil, []byte(patch)) - assert.NoError(t, err) - assert.Equal(t, expected, string(actual)) - }) - - t.Run("Handles nil patch", func(t *testing.T) { - data := `{"foo":"bar"}` - expected := `{"foo":"bar"}` - - actual, err := JsonMerge([]byte(data), nil) - assert.NoError(t, err) - assert.Equal(t, expected, string(actual)) - }) - }) - t.Run("when array", func(t *testing.T) { - t.Run("it does not merge", func(t *testing.T) { - data := `[{"foo": 1}]` - patch := `[{"foo": null}]` - expected := `[{"foo":1}]` - - actual, err := JsonMerge([]byte(data), []byte(patch)) - assert.NoError(t, err) - assert.Equal(t, expected, string(actual)) - }) - }) -} diff --git a/pkg/runtime/styleparam.go b/pkg/runtime/styleparam.go deleted file mode 100644 index 8f7e12927a..0000000000 --- a/pkg/runtime/styleparam.go +++ /dev/null @@ -1,473 +0,0 @@ -// Copyright 2019 DeepMap, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package runtime - -import ( - "bytes" - "encoding" - "encoding/json" - "errors" - "fmt" - "net/url" - "reflect" - "sort" - "strconv" - "strings" - "time" - - "github.com/deepmap/oapi-codegen/pkg/types" -) - -// Parameter escaping works differently based on where a header is found - -type ParamLocation int - -const ( - ParamLocationUndefined ParamLocation = iota - ParamLocationQuery - ParamLocationPath - ParamLocationHeader - ParamLocationCookie -) - -// StyleParam is used by older generated code, and must remain compatible -// with that code. It is not to be used in new templates. Please see the -// function below, which can specialize its output based on the location of -// the parameter. -func StyleParam(style string, explode bool, paramName string, value interface{}) (string, error) { - return StyleParamWithLocation(style, explode, paramName, ParamLocationUndefined, value) -} - -// Given an input value, such as a primitive type, array or object, turn it -// into a parameter based on style/explode definition, performing whatever -// escaping is necessary based on parameter location -func StyleParamWithLocation(style string, explode bool, paramName string, paramLocation ParamLocation, value interface{}) (string, error) { - t := reflect.TypeOf(value) - v := reflect.ValueOf(value) - - // Things may be passed in by pointer, we need to dereference, so return - // error on nil. - if t.Kind() == reflect.Ptr { - if v.IsNil() { - return "", fmt.Errorf("value is a nil pointer") - } - v = reflect.Indirect(v) - t = v.Type() - } - - // If the value implements encoding.TextMarshaler we use it for marshaling - // https://github.com/deepmap/oapi-codegen/issues/504 - if tu, ok := value.(encoding.TextMarshaler); ok { - t := reflect.Indirect(reflect.ValueOf(value)).Type() - convertableToTime := t.ConvertibleTo(reflect.TypeOf(time.Time{})) - convertableToDate := t.ConvertibleTo(reflect.TypeOf(types.Date{})) - - // Since both time.Time and types.Date implement encoding.TextMarshaler - // we should avoid calling theirs MarshalText() - if !convertableToTime && !convertableToDate { - b, err := tu.MarshalText() - if err != nil { - return "", fmt.Errorf("error marshaling '%s' as text: %s", value, err) - } - - return stylePrimitive(style, explode, paramName, paramLocation, string(b)) - } - } - - switch t.Kind() { - case reflect.Slice: - n := v.Len() - sliceVal := make([]interface{}, n) - for i := 0; i < n; i++ { - sliceVal[i] = v.Index(i).Interface() - } - return styleSlice(style, explode, paramName, paramLocation, sliceVal) - case reflect.Struct: - return styleStruct(style, explode, paramName, paramLocation, value) - case reflect.Map: - return styleMap(style, explode, paramName, paramLocation, value) - default: - return stylePrimitive(style, explode, paramName, paramLocation, value) - } -} - -func styleSlice(style string, explode bool, paramName string, paramLocation ParamLocation, values []interface{}) (string, error) { - if style == "deepObject" { - if !explode { - return "", errors.New("deepObjects must be exploded") - } - return MarshalDeepObject(values, paramName) - } - - var prefix string - var separator string - - switch style { - case "simple": - separator = "," - case "label": - prefix = "." - if explode { - separator = "." - } else { - separator = "," - } - case "matrix": - prefix = fmt.Sprintf(";%s=", paramName) - if explode { - separator = prefix - } else { - separator = "," - } - case "form": - prefix = fmt.Sprintf("%s=", paramName) - if explode { - separator = "&" + prefix - } else { - separator = "," - } - case "spaceDelimited": - prefix = fmt.Sprintf("%s=", paramName) - if explode { - separator = "&" + prefix - } else { - separator = " " - } - case "pipeDelimited": - prefix = fmt.Sprintf("%s=", paramName) - if explode { - separator = "&" + prefix - } else { - separator = "|" - } - default: - return "", fmt.Errorf("unsupported style '%s'", style) - } - - // We're going to assume here that the array is one of simple types. - var err error - var part string - parts := make([]string, len(values)) - for i, v := range values { - part, err = primitiveToString(v) - part = escapeParameterString(part, paramLocation) - parts[i] = part - if err != nil { - return "", fmt.Errorf("error formatting '%s': %s", paramName, err) - } - } - return prefix + strings.Join(parts, separator), nil -} - -func sortedKeys(strMap map[string]string) []string { - keys := make([]string, len(strMap)) - i := 0 - for k := range strMap { - keys[i] = k - i++ - } - sort.Strings(keys) - return keys -} - -// These are special cases. The value may be a date, time, or uuid, -// in which case, marshal it into the correct format. -func marshalKnownTypes(value interface{}) (string, bool) { - v := reflect.Indirect(reflect.ValueOf(value)) - t := v.Type() - - if t.ConvertibleTo(reflect.TypeOf(time.Time{})) { - tt := v.Convert(reflect.TypeOf(time.Time{})) - timeVal := tt.Interface().(time.Time) - return timeVal.Format(time.RFC3339Nano), true - } - - if t.ConvertibleTo(reflect.TypeOf(types.Date{})) { - d := v.Convert(reflect.TypeOf(types.Date{})) - dateVal := d.Interface().(types.Date) - return dateVal.Format(types.DateFormat), true - } - - if t.ConvertibleTo(reflect.TypeOf(types.UUID{})) { - u := v.Convert(reflect.TypeOf(types.UUID{})) - uuidVal := u.Interface().(types.UUID) - return uuidVal.String(), true - } - - return "", false -} - -func styleStruct(style string, explode bool, paramName string, paramLocation ParamLocation, value interface{}) (string, error) { - if timeVal, ok := marshalKnownTypes(value); ok { - styledVal, err := stylePrimitive(style, explode, paramName, paramLocation, timeVal) - if err != nil { - return "", fmt.Errorf("failed to style time: %w", err) - } - return styledVal, nil - } - - if style == "deepObject" { - if !explode { - return "", errors.New("deepObjects must be exploded") - } - return MarshalDeepObject(value, paramName) - } - - // If input has Marshaler, such as object has Additional Property or AnyOf, - // We use this Marshaler and convert into interface{} before styling. - if m, ok := value.(json.Marshaler); ok { - buf, err := m.MarshalJSON() - if err != nil { - return "", fmt.Errorf("failed to marshal input to JSON: %w", err) - } - e := json.NewDecoder(bytes.NewReader(buf)) - e.UseNumber() - var i2 interface{} - err = e.Decode(&i2) - if err != nil { - return "", fmt.Errorf("failed to unmarshal JSON: %w", err) - } - s, err := StyleParamWithLocation(style, explode, paramName, paramLocation, i2) - if err != nil { - return "", fmt.Errorf("error style JSON structure: %w", err) - } - return s, nil - } - - // Otherwise, we need to build a dictionary of the struct's fields. Each - // field may only be a primitive value. - v := reflect.ValueOf(value) - t := reflect.TypeOf(value) - fieldDict := make(map[string]string) - - for i := 0; i < t.NumField(); i++ { - fieldT := t.Field(i) - // Find the json annotation on the field, and use the json specified - // name if available, otherwise, just the field name. - tag := fieldT.Tag.Get("json") - fieldName := fieldT.Name - if tag != "" { - tagParts := strings.Split(tag, ",") - name := tagParts[0] - if name != "" { - fieldName = name - } - } - f := v.Field(i) - - // Unset optional fields will be nil pointers, skip over those. - if f.Type().Kind() == reflect.Ptr && f.IsNil() { - continue - } - str, err := primitiveToString(f.Interface()) - if err != nil { - return "", fmt.Errorf("error formatting '%s': %s", paramName, err) - } - fieldDict[fieldName] = str - } - - return processFieldDict(style, explode, paramName, paramLocation, fieldDict) -} - -func styleMap(style string, explode bool, paramName string, paramLocation ParamLocation, value interface{}) (string, error) { - if style == "deepObject" { - if !explode { - return "", errors.New("deepObjects must be exploded") - } - return MarshalDeepObject(value, paramName) - } - - dict, ok := value.(map[string]interface{}) - if !ok { - return "", errors.New("map not of type map[string]interface{}") - } - - fieldDict := make(map[string]string) - for fieldName, value := range dict { - str, err := primitiveToString(value) - if err != nil { - return "", fmt.Errorf("error formatting '%s': %s", paramName, err) - } - fieldDict[fieldName] = str - } - return processFieldDict(style, explode, paramName, paramLocation, fieldDict) -} - -func processFieldDict(style string, explode bool, paramName string, paramLocation ParamLocation, fieldDict map[string]string) (string, error) { - var parts []string - - // This works for everything except deepObject. We'll handle that one - // separately. - if style != "deepObject" { - if explode { - for _, k := range sortedKeys(fieldDict) { - v := escapeParameterString(fieldDict[k], paramLocation) - parts = append(parts, k+"="+v) - } - } else { - for _, k := range sortedKeys(fieldDict) { - v := escapeParameterString(fieldDict[k], paramLocation) - parts = append(parts, k) - parts = append(parts, v) - } - } - } - - var prefix string - var separator string - - switch style { - case "simple": - separator = "," - case "label": - prefix = "." - if explode { - separator = prefix - } else { - separator = "," - } - case "matrix": - if explode { - separator = ";" - prefix = ";" - } else { - separator = "," - prefix = fmt.Sprintf(";%s=", paramName) - } - case "form": - if explode { - separator = "&" - } else { - prefix = fmt.Sprintf("%s=", paramName) - separator = "," - } - case "deepObject": - { - if !explode { - return "", fmt.Errorf("deepObject parameters must be exploded") - } - for _, k := range sortedKeys(fieldDict) { - v := fieldDict[k] - part := fmt.Sprintf("%s[%s]=%s", paramName, k, v) - parts = append(parts, part) - } - separator = "&" - } - default: - return "", fmt.Errorf("unsupported style '%s'", style) - } - - return prefix + strings.Join(parts, separator), nil -} - -func stylePrimitive(style string, explode bool, paramName string, paramLocation ParamLocation, value interface{}) (string, error) { - strVal, err := primitiveToString(value) - if err != nil { - return "", err - } - - var prefix string - switch style { - case "simple": - case "label": - prefix = "." - case "matrix": - prefix = fmt.Sprintf(";%s=", paramName) - case "form": - prefix = fmt.Sprintf("%s=", paramName) - default: - return "", fmt.Errorf("unsupported style '%s'", style) - } - return prefix + escapeParameterString(strVal, paramLocation), nil -} - -// Converts a primitive value to a string. We need to do this based on the -// Kind of an interface, not the Type to work with aliased types. -func primitiveToString(value interface{}) (string, error) { - var output string - - // sometimes time and date used like primitive types - // it can happen if paramether is object and has time or date as field - if res, ok := marshalKnownTypes(value); ok { - return res, nil - } - - // Values may come in by pointer for optionals, so make sure to dereferene. - v := reflect.Indirect(reflect.ValueOf(value)) - t := v.Type() - kind := t.Kind() - - switch kind { - case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: - output = strconv.FormatInt(v.Int(), 10) - case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: - output = strconv.FormatUint(v.Uint(), 10) - case reflect.Float64: - output = strconv.FormatFloat(v.Float(), 'f', -1, 64) - case reflect.Float32: - output = strconv.FormatFloat(v.Float(), 'f', -1, 32) - case reflect.Bool: - if v.Bool() { - output = "true" - } else { - output = "false" - } - case reflect.String: - output = v.String() - case reflect.Struct: - // If input has Marshaler, such as object has Additional Property or AnyOf, - // We use this Marshaler and convert into interface{} before styling. - if m, ok := value.(json.Marshaler); ok { - buf, err := m.MarshalJSON() - if err != nil { - return "", fmt.Errorf("failed to marshal input to JSON: %w", err) - } - e := json.NewDecoder(bytes.NewReader(buf)) - e.UseNumber() - var i2 interface{} - err = e.Decode(&i2) - if err != nil { - return "", fmt.Errorf("failed to unmarshal JSON: %w", err) - } - output, err = primitiveToString(i2) - if err != nil { - return "", fmt.Errorf("error convert JSON structure: %w", err) - } - break - } - fallthrough - default: - v, ok := value.(fmt.Stringer) - if !ok { - return "", fmt.Errorf("unsupported type %s", reflect.TypeOf(value).String()) - } - - output = v.String() - } - return output, nil -} - -// This function escapes a parameter value bas on the location of that parameter. -// Query params and path params need different kinds of escaping, while header -// and cookie params seem not to need escaping. -func escapeParameterString(value string, paramLocation ParamLocation) string { - switch paramLocation { - case ParamLocationQuery: - return url.QueryEscape(value) - case ParamLocationPath: - return url.PathEscape(value) - default: - return value - } -} diff --git a/pkg/runtime/styleparam_test.go b/pkg/runtime/styleparam_test.go deleted file mode 100644 index 1943ae0fcd..0000000000 --- a/pkg/runtime/styleparam_test.go +++ /dev/null @@ -1,692 +0,0 @@ -// Copyright 2019 DeepMap, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package runtime - -import ( - "testing" - "time" - - "github.com/deepmap/oapi-codegen/pkg/types" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" -) - -func TestStyleParam(t *testing.T) { - primitive := 5 - primitiveString := "123" - primitiveStringWithReservedChar := "123;456" - array := []int{3, 4, 5} - type TestObject struct { - FirstName string `json:"firstName"` - Role string `json:"role"` - } - object := TestObject{ - FirstName: "Alex", - Role: "admin", - } - dict := map[string]interface{}{} - dict["firstName"] = "Alex" - dict["role"] = "admin" - - type AliasedTime time.Time - ti, _ := time.Parse(time.RFC3339, "2020-01-01T22:00:00+02:00") - timestamp := AliasedTime(ti) - - type AliasedDate types.Date - date := AliasedDate{Time: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)} - - type AliasedUUID types.UUID - aUUID := AliasedUUID(uuid.MustParse("baa07328-452e-40bd-aa2e-fa823ec13605")) - - // ---------------------------- Simple Style ------------------------------- - - result, err := StyleParamWithLocation("simple", false, "id", ParamLocationQuery, primitive) - assert.NoError(t, err) - assert.EqualValues(t, "5", result) - - result, err = StyleParamWithLocation("simple", true, "id", ParamLocationQuery, primitive) - assert.NoError(t, err) - assert.EqualValues(t, "5", result) - - result, err = StyleParamWithLocation("simple", false, "id", ParamLocationQuery, array) - assert.NoError(t, err) - assert.EqualValues(t, "3,4,5", result) - - result, err = StyleParamWithLocation("simple", true, "id", ParamLocationQuery, array) - assert.NoError(t, err) - assert.EqualValues(t, "3,4,5", result) - - result, err = StyleParamWithLocation("simple", false, "id", ParamLocationQuery, object) - assert.NoError(t, err) - assert.EqualValues(t, "firstName,Alex,role,admin", result) - - result, err = StyleParamWithLocation("simple", true, "id", ParamLocationQuery, object) - assert.NoError(t, err) - assert.EqualValues(t, "firstName=Alex,role=admin", result) - - result, err = StyleParamWithLocation("simple", false, "id", ParamLocationQuery, dict) - assert.NoError(t, err) - assert.EqualValues(t, "firstName,Alex,role,admin", result) - - result, err = StyleParamWithLocation("simple", true, "id", ParamLocationQuery, dict) - assert.NoError(t, err) - assert.EqualValues(t, "firstName=Alex,role=admin", result) - - result, err = StyleParamWithLocation("simple", false, "id", ParamLocationQuery, timestamp) - assert.NoError(t, err) - assert.EqualValues(t, "2020-01-01T22%3A00%3A00%2B02%3A00", result) - - result, err = StyleParamWithLocation("simple", true, "id", ParamLocationQuery, timestamp) - assert.NoError(t, err) - assert.EqualValues(t, "2020-01-01T22%3A00%3A00%2B02%3A00", result) - - result, err = StyleParamWithLocation("simple", false, "id", ParamLocationQuery, ×tamp) - assert.NoError(t, err) - assert.EqualValues(t, "2020-01-01T22%3A00%3A00%2B02%3A00", result) - - result, err = StyleParamWithLocation("simple", true, "id", ParamLocationQuery, ×tamp) - assert.NoError(t, err) - assert.EqualValues(t, "2020-01-01T22%3A00%3A00%2B02%3A00", result) - - result, err = StyleParamWithLocation("simple", false, "id", ParamLocationQuery, date) - assert.NoError(t, err) - assert.EqualValues(t, "2020-01-01", result) - - result, err = StyleParamWithLocation("simple", true, "id", ParamLocationQuery, date) - assert.NoError(t, err) - assert.EqualValues(t, "2020-01-01", result) - - result, err = StyleParamWithLocation("simple", false, "id", ParamLocationQuery, &date) - assert.NoError(t, err) - assert.EqualValues(t, "2020-01-01", result) - - result, err = StyleParamWithLocation("simple", true, "id", ParamLocationQuery, &date) - assert.NoError(t, err) - assert.EqualValues(t, "2020-01-01", result) - - result, err = StyleParamWithLocation("simple", false, "id", ParamLocationQuery, aUUID) - assert.NoError(t, err) - assert.EqualValues(t, "baa07328-452e-40bd-aa2e-fa823ec13605", result) - - result, err = StyleParamWithLocation("simple", true, "id", ParamLocationQuery, aUUID) - assert.NoError(t, err) - assert.EqualValues(t, "baa07328-452e-40bd-aa2e-fa823ec13605", result) - - result, err = StyleParamWithLocation("simple", false, "id", ParamLocationQuery, &aUUID) - assert.NoError(t, err) - assert.EqualValues(t, "baa07328-452e-40bd-aa2e-fa823ec13605", result) - - result, err = StyleParamWithLocation("simple", true, "id", ParamLocationQuery, &aUUID) - assert.NoError(t, err) - assert.EqualValues(t, "baa07328-452e-40bd-aa2e-fa823ec13605", result) - - // ----------------------------- Label Style ------------------------------- - - result, err = StyleParamWithLocation("label", false, "id", ParamLocationQuery, primitive) - assert.NoError(t, err) - assert.EqualValues(t, ".5", result) - - result, err = StyleParamWithLocation("label", true, "id", ParamLocationQuery, primitive) - assert.NoError(t, err) - assert.EqualValues(t, ".5", result) - - result, err = StyleParamWithLocation("label", false, "id", ParamLocationQuery, array) - assert.NoError(t, err) - assert.EqualValues(t, ".3,4,5", result) - - result, err = StyleParamWithLocation("label", true, "id", ParamLocationQuery, array) - assert.NoError(t, err) - assert.EqualValues(t, ".3.4.5", result) - - result, err = StyleParamWithLocation("label", false, "id", ParamLocationQuery, object) - assert.NoError(t, err) - assert.EqualValues(t, ".firstName,Alex,role,admin", result) - - result, err = StyleParamWithLocation("label", true, "id", ParamLocationQuery, object) - assert.NoError(t, err) - assert.EqualValues(t, ".firstName=Alex.role=admin", result) - - result, err = StyleParamWithLocation("label", false, "id", ParamLocationQuery, dict) - assert.NoError(t, err) - assert.EqualValues(t, ".firstName,Alex,role,admin", result) - - result, err = StyleParamWithLocation("label", true, "id", ParamLocationQuery, dict) - assert.NoError(t, err) - assert.EqualValues(t, ".firstName=Alex.role=admin", result) - - result, err = StyleParamWithLocation("label", false, "id", ParamLocationQuery, timestamp) - assert.NoError(t, err) - assert.EqualValues(t, ".2020-01-01T22%3A00%3A00%2B02%3A00", result) - - result, err = StyleParamWithLocation("label", true, "id", ParamLocationQuery, timestamp) - assert.NoError(t, err) - assert.EqualValues(t, ".2020-01-01T22%3A00%3A00%2B02%3A00", result) - - result, err = StyleParamWithLocation("label", false, "id", ParamLocationQuery, ×tamp) - assert.NoError(t, err) - assert.EqualValues(t, ".2020-01-01T22%3A00%3A00%2B02%3A00", result) - - result, err = StyleParamWithLocation("label", true, "id", ParamLocationQuery, ×tamp) - assert.NoError(t, err) - assert.EqualValues(t, ".2020-01-01T22%3A00%3A00%2B02%3A00", result) - - result, err = StyleParamWithLocation("label", false, "id", ParamLocationQuery, date) - assert.NoError(t, err) - assert.EqualValues(t, ".2020-01-01", result) - - result, err = StyleParamWithLocation("label", true, "id", ParamLocationQuery, date) - assert.NoError(t, err) - assert.EqualValues(t, ".2020-01-01", result) - - result, err = StyleParamWithLocation("label", false, "id", ParamLocationQuery, &date) - assert.NoError(t, err) - assert.EqualValues(t, ".2020-01-01", result) - - result, err = StyleParamWithLocation("label", true, "id", ParamLocationQuery, &date) - assert.NoError(t, err) - assert.EqualValues(t, ".2020-01-01", result) - - result, err = StyleParamWithLocation("label", false, "id", ParamLocationQuery, aUUID) - assert.NoError(t, err) - assert.EqualValues(t, ".baa07328-452e-40bd-aa2e-fa823ec13605", result) - - result, err = StyleParamWithLocation("label", true, "id", ParamLocationQuery, aUUID) - assert.NoError(t, err) - assert.EqualValues(t, ".baa07328-452e-40bd-aa2e-fa823ec13605", result) - - result, err = StyleParamWithLocation("label", false, "id", ParamLocationQuery, &aUUID) - assert.NoError(t, err) - assert.EqualValues(t, ".baa07328-452e-40bd-aa2e-fa823ec13605", result) - - result, err = StyleParamWithLocation("label", true, "id", ParamLocationQuery, &aUUID) - assert.NoError(t, err) - assert.EqualValues(t, ".baa07328-452e-40bd-aa2e-fa823ec13605", result) - - // ----------------------------- Matrix Style ------------------------------ - - result, err = StyleParamWithLocation("matrix", false, "id", ParamLocationQuery, primitive) - assert.NoError(t, err) - assert.EqualValues(t, ";id=5", result) - - result, err = StyleParamWithLocation("matrix", true, "id", ParamLocationQuery, primitive) - assert.NoError(t, err) - assert.EqualValues(t, ";id=5", result) - - result, err = StyleParamWithLocation("matrix", false, "id", ParamLocationQuery, array) - assert.NoError(t, err) - assert.EqualValues(t, ";id=3,4,5", result) - - result, err = StyleParamWithLocation("matrix", true, "id", ParamLocationQuery, array) - assert.NoError(t, err) - assert.EqualValues(t, ";id=3;id=4;id=5", result) - - result, err = StyleParamWithLocation("matrix", false, "id", ParamLocationQuery, object) - assert.NoError(t, err) - assert.EqualValues(t, ";id=firstName,Alex,role,admin", result) - - result, err = StyleParamWithLocation("matrix", true, "id", ParamLocationQuery, object) - assert.NoError(t, err) - assert.EqualValues(t, ";firstName=Alex;role=admin", result) - - result, err = StyleParamWithLocation("matrix", false, "id", ParamLocationQuery, dict) - assert.NoError(t, err) - assert.EqualValues(t, ";id=firstName,Alex,role,admin", result) - - result, err = StyleParamWithLocation("matrix", true, "id", ParamLocationQuery, dict) - assert.NoError(t, err) - assert.EqualValues(t, ";firstName=Alex;role=admin", result) - - result, err = StyleParamWithLocation("matrix", false, "id", ParamLocationQuery, timestamp) - assert.NoError(t, err) - assert.EqualValues(t, ";id=2020-01-01T22%3A00%3A00%2B02%3A00", result) - - result, err = StyleParamWithLocation("matrix", true, "id", ParamLocationQuery, timestamp) - assert.NoError(t, err) - assert.EqualValues(t, ";id=2020-01-01T22%3A00%3A00%2B02%3A00", result) - - result, err = StyleParamWithLocation("matrix", false, "id", ParamLocationQuery, ×tamp) - assert.NoError(t, err) - assert.EqualValues(t, ";id=2020-01-01T22%3A00%3A00%2B02%3A00", result) - - result, err = StyleParamWithLocation("matrix", true, "id", ParamLocationQuery, ×tamp) - assert.NoError(t, err) - assert.EqualValues(t, ";id=2020-01-01T22%3A00%3A00%2B02%3A00", result) - - result, err = StyleParamWithLocation("matrix", false, "id", ParamLocationQuery, date) - assert.NoError(t, err) - assert.EqualValues(t, ";id=2020-01-01", result) - - result, err = StyleParamWithLocation("matrix", true, "id", ParamLocationQuery, date) - assert.NoError(t, err) - assert.EqualValues(t, ";id=2020-01-01", result) - - result, err = StyleParamWithLocation("matrix", false, "id", ParamLocationQuery, &date) - assert.NoError(t, err) - assert.EqualValues(t, ";id=2020-01-01", result) - - result, err = StyleParamWithLocation("matrix", true, "id", ParamLocationQuery, &date) - assert.NoError(t, err) - assert.EqualValues(t, ";id=2020-01-01", result) - - result, err = StyleParamWithLocation("matrix", false, "id", ParamLocationQuery, aUUID) - assert.NoError(t, err) - assert.EqualValues(t, ";id=baa07328-452e-40bd-aa2e-fa823ec13605", result) - - result, err = StyleParamWithLocation("matrix", true, "id", ParamLocationQuery, aUUID) - assert.NoError(t, err) - assert.EqualValues(t, ";id=baa07328-452e-40bd-aa2e-fa823ec13605", result) - - result, err = StyleParamWithLocation("matrix", false, "id", ParamLocationQuery, &aUUID) - assert.NoError(t, err) - assert.EqualValues(t, ";id=baa07328-452e-40bd-aa2e-fa823ec13605", result) - - result, err = StyleParamWithLocation("matrix", true, "id", ParamLocationQuery, &aUUID) - assert.NoError(t, err) - assert.EqualValues(t, ";id=baa07328-452e-40bd-aa2e-fa823ec13605", result) - - // ------------------------------ Form Style ------------------------------- - result, err = StyleParamWithLocation("form", false, "id", ParamLocationQuery, primitive) - assert.NoError(t, err) - assert.EqualValues(t, "id=5", result) - - result, err = StyleParamWithLocation("form", true, "id", ParamLocationQuery, primitive) - assert.NoError(t, err) - assert.EqualValues(t, "id=5", result) - - result, err = StyleParamWithLocation("form", false, "id", ParamLocationQuery, primitiveString) - assert.NoError(t, err) - assert.EqualValues(t, "id=123", result) - - result, err = StyleParamWithLocation("form", true, "id", ParamLocationQuery, primitiveString) - assert.NoError(t, err) - assert.EqualValues(t, "id=123", result) - - result, err = StyleParamWithLocation("form", false, "id", ParamLocationQuery, primitiveStringWithReservedChar) - assert.NoError(t, err) - assert.EqualValues(t, "id=123%3B456", result) - - result, err = StyleParamWithLocation("form", true, "id", ParamLocationQuery, primitiveStringWithReservedChar) - assert.NoError(t, err) - assert.EqualValues(t, "id=123%3B456", result) - - result, err = StyleParamWithLocation("form", false, "id", ParamLocationQuery, array) - assert.NoError(t, err) - assert.EqualValues(t, "id=3,4,5", result) - - result, err = StyleParamWithLocation("form", true, "id", ParamLocationQuery, array) - assert.NoError(t, err) - assert.EqualValues(t, "id=3&id=4&id=5", result) - - result, err = StyleParamWithLocation("form", false, "id", ParamLocationQuery, object) - assert.NoError(t, err) - assert.EqualValues(t, "id=firstName,Alex,role,admin", result) - - result, err = StyleParamWithLocation("form", true, "id", ParamLocationQuery, object) - assert.NoError(t, err) - assert.EqualValues(t, "firstName=Alex&role=admin", result) - - result, err = StyleParamWithLocation("form", false, "id", ParamLocationQuery, dict) - assert.NoError(t, err) - assert.EqualValues(t, "id=firstName,Alex,role,admin", result) - - result, err = StyleParamWithLocation("form", true, "id", ParamLocationQuery, dict) - assert.NoError(t, err) - assert.EqualValues(t, "firstName=Alex&role=admin", result) - - result, err = StyleParamWithLocation("form", false, "id", ParamLocationQuery, timestamp) - assert.NoError(t, err) - assert.EqualValues(t, "id=2020-01-01T22%3A00%3A00%2B02%3A00", result) - - result, err = StyleParamWithLocation("form", true, "id", ParamLocationQuery, timestamp) - assert.NoError(t, err) - assert.EqualValues(t, "id=2020-01-01T22%3A00%3A00%2B02%3A00", result) - - result, err = StyleParamWithLocation("form", false, "id", ParamLocationQuery, ×tamp) - assert.NoError(t, err) - assert.EqualValues(t, "id=2020-01-01T22%3A00%3A00%2B02%3A00", result) - - result, err = StyleParamWithLocation("form", true, "id", ParamLocationQuery, ×tamp) - assert.NoError(t, err) - assert.EqualValues(t, "id=2020-01-01T22%3A00%3A00%2B02%3A00", result) - - result, err = StyleParamWithLocation("form", false, "id", ParamLocationQuery, date) - assert.NoError(t, err) - assert.EqualValues(t, "id=2020-01-01", result) - - result, err = StyleParamWithLocation("form", true, "id", ParamLocationQuery, date) - assert.NoError(t, err) - assert.EqualValues(t, "id=2020-01-01", result) - - result, err = StyleParamWithLocation("form", false, "id", ParamLocationQuery, &date) - assert.NoError(t, err) - assert.EqualValues(t, "id=2020-01-01", result) - - result, err = StyleParamWithLocation("form", true, "id", ParamLocationQuery, &date) - assert.NoError(t, err) - assert.EqualValues(t, "id=2020-01-01", result) - - result, err = StyleParamWithLocation("form", false, "id", ParamLocationQuery, aUUID) - assert.NoError(t, err) - assert.EqualValues(t, "id=baa07328-452e-40bd-aa2e-fa823ec13605", result) - - result, err = StyleParamWithLocation("form", true, "id", ParamLocationQuery, aUUID) - assert.NoError(t, err) - assert.EqualValues(t, "id=baa07328-452e-40bd-aa2e-fa823ec13605", result) - - result, err = StyleParamWithLocation("form", false, "id", ParamLocationQuery, &aUUID) - assert.NoError(t, err) - assert.EqualValues(t, "id=baa07328-452e-40bd-aa2e-fa823ec13605", result) - - result, err = StyleParamWithLocation("form", true, "id", ParamLocationQuery, &aUUID) - assert.NoError(t, err) - assert.EqualValues(t, "id=baa07328-452e-40bd-aa2e-fa823ec13605", result) - - // ------------------------ spaceDelimited Style -------------------------- - - _, err = StyleParamWithLocation("spaceDelimited", false, "id", ParamLocationQuery, primitive) - assert.Error(t, err) - - _, err = StyleParamWithLocation("spaceDelimited", true, "id", ParamLocationQuery, primitive) - assert.Error(t, err) - - result, err = StyleParamWithLocation("spaceDelimited", false, "id", ParamLocationQuery, array) - assert.NoError(t, err) - assert.EqualValues(t, "id=3 4 5", result) - - result, err = StyleParamWithLocation("spaceDelimited", true, "id", ParamLocationQuery, array) - assert.NoError(t, err) - assert.EqualValues(t, "id=3&id=4&id=5", result) - - _, err = StyleParamWithLocation("spaceDelimited", false, "id", ParamLocationQuery, object) - assert.Error(t, err) - - _, err = StyleParamWithLocation("spaceDelimited", true, "id", ParamLocationQuery, object) - assert.Error(t, err) - - _, err = StyleParamWithLocation("spaceDelimited", false, "id", ParamLocationQuery, dict) - assert.Error(t, err) - - _, err = StyleParamWithLocation("spaceDelimited", true, "id", ParamLocationQuery, dict) - assert.Error(t, err) - - _, err = StyleParamWithLocation("spaceDelimited", false, "id", ParamLocationQuery, timestamp) - assert.Error(t, err) - - _, err = StyleParamWithLocation("spaceDelimited", true, "id", ParamLocationQuery, timestamp) - assert.Error(t, err) - - _, err = StyleParamWithLocation("spaceDelimited", false, "id", ParamLocationQuery, ×tamp) - assert.Error(t, err) - - _, err = StyleParamWithLocation("spaceDelimited", true, "id", ParamLocationQuery, ×tamp) - assert.Error(t, err) - - _, err = StyleParamWithLocation("spaceDelimited", false, "id", ParamLocationQuery, date) - assert.Error(t, err) - - _, err = StyleParamWithLocation("spaceDelimited", true, "id", ParamLocationQuery, date) - assert.Error(t, err) - - _, err = StyleParamWithLocation("spaceDelimited", false, "id", ParamLocationQuery, &date) - assert.Error(t, err) - - _, err = StyleParamWithLocation("spaceDelimited", true, "id", ParamLocationQuery, &date) - assert.Error(t, err) - - _, err = StyleParamWithLocation("spaceDelimited", false, "id", ParamLocationQuery, aUUID) - assert.Error(t, err) - - _, err = StyleParamWithLocation("spaceDelimited", true, "id", ParamLocationQuery, aUUID) - assert.Error(t, err) - - _, err = StyleParamWithLocation("spaceDelimited", false, "id", ParamLocationQuery, &aUUID) - assert.Error(t, err) - - _, err = StyleParamWithLocation("spaceDelimited", true, "id", ParamLocationQuery, &aUUID) - assert.Error(t, err) - - // ------------------------- pipeDelimited Style -------------------------- - - _, err = StyleParamWithLocation("pipeDelimited", false, "id", ParamLocationQuery, primitive) - assert.Error(t, err) - - _, err = StyleParamWithLocation("pipeDelimited", true, "id", ParamLocationQuery, primitive) - assert.Error(t, err) - - result, err = StyleParamWithLocation("pipeDelimited", false, "id", ParamLocationQuery, array) - assert.NoError(t, err) - assert.EqualValues(t, "id=3|4|5", result) - - result, err = StyleParamWithLocation("pipeDelimited", true, "id", ParamLocationQuery, array) - assert.NoError(t, err) - assert.EqualValues(t, "id=3&id=4&id=5", result) - - _, err = StyleParamWithLocation("pipeDelimited", false, "id", ParamLocationQuery, object) - assert.Error(t, err) - - _, err = StyleParamWithLocation("pipeDelimited", true, "id", ParamLocationQuery, object) - assert.Error(t, err) - - _, err = StyleParamWithLocation("pipeDelimited", false, "id", ParamLocationQuery, dict) - assert.Error(t, err) - - _, err = StyleParamWithLocation("pipeDelimited", true, "id", ParamLocationQuery, dict) - assert.Error(t, err) - - _, err = StyleParamWithLocation("pipeDelimited", false, "id", ParamLocationQuery, timestamp) - assert.Error(t, err) - - _, err = StyleParamWithLocation("pipeDelimited", true, "id", ParamLocationQuery, timestamp) - assert.Error(t, err) - - _, err = StyleParamWithLocation("pipeDelimited", false, "id", ParamLocationQuery, ×tamp) - assert.Error(t, err) - - _, err = StyleParamWithLocation("pipeDelimited", true, "id", ParamLocationQuery, ×tamp) - assert.Error(t, err) - - _, err = StyleParamWithLocation("pipeDelimited", false, "id", ParamLocationQuery, date) - assert.Error(t, err) - - _, err = StyleParamWithLocation("pipeDelimited", true, "id", ParamLocationQuery, date) - assert.Error(t, err) - - _, err = StyleParamWithLocation("pipeDelimited", false, "id", ParamLocationQuery, &date) - assert.Error(t, err) - - _, err = StyleParamWithLocation("pipeDelimited", true, "id", ParamLocationQuery, &date) - assert.Error(t, err) - - _, err = StyleParamWithLocation("pipeDelimited", false, "id", ParamLocationQuery, aUUID) - assert.Error(t, err) - - _, err = StyleParamWithLocation("pipeDelimited", true, "id", ParamLocationQuery, aUUID) - assert.Error(t, err) - - _, err = StyleParamWithLocation("pipeDelimited", false, "id", ParamLocationQuery, &aUUID) - assert.Error(t, err) - - _, err = StyleParamWithLocation("pipeDelimited", true, "id", ParamLocationQuery, &aUUID) - assert.Error(t, err) - - // --------------------------- deepObject Style --------------------------- - _, err = StyleParamWithLocation("deepObject", false, "id", ParamLocationQuery, primitive) - assert.Error(t, err) - - _, err = StyleParamWithLocation("deepObject", true, "id", ParamLocationQuery, primitive) - assert.Error(t, err) - - _, err = StyleParamWithLocation("deepObject", false, "id", ParamLocationQuery, array) - assert.Error(t, err) - - result, err = StyleParamWithLocation("deepObject", true, "id", ParamLocationQuery, array) - assert.NoError(t, err) - assert.EqualValues(t, "id[0]=3&id[1]=4&id[2]=5", result) - - _, err = StyleParamWithLocation("deepObject", false, "id", ParamLocationQuery, object) - assert.Error(t, err) - - result, err = StyleParamWithLocation("deepObject", true, "id", ParamLocationQuery, object) - assert.NoError(t, err) - assert.EqualValues(t, "id[firstName]=Alex&id[role]=admin", result) - - result, err = StyleParamWithLocation("deepObject", true, "id", ParamLocationQuery, dict) - assert.NoError(t, err) - assert.EqualValues(t, "id[firstName]=Alex&id[role]=admin", result) - - _, err = StyleParamWithLocation("deepObject", false, "id", ParamLocationQuery, timestamp) - assert.Error(t, err) - - _, err = StyleParamWithLocation("deepObject", true, "id", ParamLocationQuery, timestamp) - assert.Error(t, err) - - _, err = StyleParamWithLocation("deepObject", false, "id", ParamLocationQuery, ×tamp) - assert.Error(t, err) - - _, err = StyleParamWithLocation("deepObject", true, "id", ParamLocationQuery, ×tamp) - assert.Error(t, err) - - _, err = StyleParamWithLocation("deepObject", false, "id", ParamLocationQuery, date) - assert.Error(t, err) - - _, err = StyleParamWithLocation("deepObject", true, "id", ParamLocationQuery, date) - assert.Error(t, err) - - _, err = StyleParamWithLocation("deepObject", false, "id", ParamLocationQuery, &date) - assert.Error(t, err) - - _, err = StyleParamWithLocation("deepObject", true, "id", ParamLocationQuery, &date) - assert.Error(t, err) - - _, err = StyleParamWithLocation("deepObject", false, "id", ParamLocationQuery, aUUID) - assert.Error(t, err) - - _, err = StyleParamWithLocation("deepObject", true, "id", ParamLocationQuery, aUUID) - assert.Error(t, err) - - _, err = StyleParamWithLocation("deepObject", false, "id", ParamLocationQuery, &aUUID) - assert.Error(t, err) - - _, err = StyleParamWithLocation("deepObject", true, "id", ParamLocationQuery, &aUUID) - assert.Error(t, err) - - // Misc tests - // Test type aliases - type StrType string - result, err = StyleParamWithLocation("simple", false, "foo", ParamLocationQuery, StrType("test")) - assert.NoError(t, err) - assert.EqualValues(t, "test", result) - - type IntType int32 - result, err = StyleParamWithLocation("simple", false, "foo", ParamLocationQuery, IntType(7)) - assert.NoError(t, err) - assert.EqualValues(t, "7", result) - - type UintType uint - result, err = StyleParamWithLocation("simple", false, "foo", ParamLocationQuery, UintType(9)) - assert.NoError(t, err) - assert.EqualValues(t, "9", result) - - type Uint8Type uint8 - result, err = StyleParamWithLocation("simple", false, "foo", ParamLocationQuery, Uint8Type(9)) - assert.NoError(t, err) - assert.EqualValues(t, "9", result) - - type Uint16Type uint16 - result, err = StyleParamWithLocation("simple", false, "foo", ParamLocationQuery, Uint16Type(9)) - assert.NoError(t, err) - assert.EqualValues(t, "9", result) - - type Uint32Type uint32 - result, err = StyleParamWithLocation("simple", false, "foo", ParamLocationQuery, Uint32Type(9)) - assert.NoError(t, err) - assert.EqualValues(t, "9", result) - - type Uint64Type uint64 - result, err = StyleParamWithLocation("simple", false, "foo", ParamLocationQuery, Uint64Type(9)) - assert.NoError(t, err) - assert.EqualValues(t, "9", result) - - type FloatType64 float64 - result, err = StyleParamWithLocation("simple", false, "foo", ParamLocationQuery, FloatType64(7.5)) - assert.NoError(t, err) - assert.EqualValues(t, "7.5", result) - - type FloatType32 float32 - result, err = StyleParamWithLocation("simple", false, "foo", ParamLocationQuery, FloatType32(1.05)) - assert.NoError(t, err) - assert.EqualValues(t, "1.05", result) - - uuidValue := uuid.MustParse("c2d07ba4-5106-4eab-bcad-0bd6068dcb1a") - result, err = StyleParamWithLocation("simple", false, "foo", ParamLocationQuery, types.UUID(uuidValue)) - assert.NoError(t, err) - assert.EqualValues(t, "c2d07ba4-5106-4eab-bcad-0bd6068dcb1a", result) - - // Test that we handle optional fields - type TestObject2 struct { - FirstName *string `json:"firstName"` - Role *string `json:"role"` - } - name := "Alex" - role := "admin" - object2 := TestObject2{ - FirstName: &name, - Role: &role, - } - result, err = StyleParamWithLocation("simple", false, "id", ParamLocationQuery, object2) - assert.NoError(t, err) - assert.EqualValues(t, "firstName,Alex,role,admin", result) - - // Nullable fields need to be excluded when null - object2.Role = nil - result, err = StyleParamWithLocation("simple", false, "id", ParamLocationQuery, object2) - assert.NoError(t, err) - assert.EqualValues(t, "firstName,Alex", result) - - // Test handling of other known types inside objects - type testObject3 struct { - TimeField time.Time `json:"time_field"` - DateField types.Date `json:"date_field"` - UUIDField types.UUID `json:"uuid_field"` - } - timeVal := time.Date(1996, time.March, 19, 0, 0, 0, 0, time.UTC) - dateVal := types.Date{ - Time: timeVal, - } - uuidVal := uuid.MustParse("baa07328-452e-40bd-aa2e-fa823ec13605") - - object3 := testObject3{ - TimeField: timeVal, - DateField: dateVal, - UUIDField: uuidVal, - } - - result, err = StyleParamWithLocation("simple", false, "id", ParamLocationQuery, object3) - assert.NoError(t, err) - assert.EqualValues(t, "date_field,1996-03-19,time_field,1996-03-19T00%3A00%3A00Z,uuid_field,baa07328-452e-40bd-aa2e-fa823ec13605", result) - - // Test handling of struct that implement encoding.TextMarshaler - timeVal = time.Date(1996, time.March, 19, 0, 0, 0, 0, time.UTC) - - result, err = StyleParamWithLocation("simple", false, "id", ParamLocationQuery, timeVal) - assert.NoError(t, err) - assert.EqualValues(t, "1996-03-19T00%3A00%3A00Z", result) - - uuidD := uuid.MustParse("972beb41-e5ea-4b31-a79a-96f4999d8769") - - result, err = StyleParamWithLocation("simple", false, "id", ParamLocationQuery, uuidD) - assert.NoError(t, err) - assert.EqualValues(t, "972beb41-e5ea-4b31-a79a-96f4999d8769", result) - -} diff --git a/pkg/securityprovider/securityprovider.go b/pkg/securityprovider/securityprovider.go index 940fc5b472..091c1765e1 100644 --- a/pkg/securityprovider/securityprovider.go +++ b/pkg/securityprovider/securityprovider.go @@ -47,7 +47,7 @@ func (s *SecurityProviderBasicAuth) Intercept(ctx context.Context, req *http.Req } // NewSecurityProviderBearerToken provides a SecurityProvider, which can solve -// the Bearer Auth challende for api-calls. +// the Bearer Auth challenge for api-calls. func NewSecurityProviderBearerToken(token string) (*SecurityProviderBearerToken, error) { return &SecurityProviderBearerToken{ token: token, diff --git a/pkg/securityprovider/securityprovider_test.go b/pkg/securityprovider/securityprovider_test.go deleted file mode 100644 index 0bb3d3cf3b..0000000000 --- a/pkg/securityprovider/securityprovider_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package securityprovider - -import ( - "testing" - - "github.com/deepmap/oapi-codegen/internal/test/client" - "github.com/stretchr/testify/assert" -) - -var ( - withTrailingSlash = "https://my-api.com/some-base-url/v1/" -) - -func TestSecurityProviders(t *testing.T) { - bearer, err := NewSecurityProviderBearerToken("mytoken") - assert.NoError(t, err) - client1, err := client.NewClient( - withTrailingSlash, - client.WithRequestEditorFn(bearer.Intercept), - ) - assert.NoError(t, err) - - apiKey, err := NewSecurityProviderApiKey("cookie", "apikey", "mykey") - assert.NoError(t, err) - client2, err := client.NewClient( - withTrailingSlash, - client.WithRequestEditorFn(apiKey.Intercept), - ) - assert.NoError(t, err) - - basicAuth, err := NewSecurityProviderBasicAuth("username", "password") - assert.NoError(t, err) - client3, err := client.NewClient( - withTrailingSlash, - client.WithRequestEditorFn(basicAuth.Intercept), - ) - assert.NoError(t, err) - - assert.Equal(t, withTrailingSlash, client1.Server) - assert.Equal(t, withTrailingSlash, client2.Server) - assert.Equal(t, withTrailingSlash, client3.Server) -} diff --git a/pkg/testutil/request_helpers.go b/pkg/testutil/request_helpers.go deleted file mode 100644 index e54f535318..0000000000 --- a/pkg/testutil/request_helpers.go +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2019 DeepMap, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package testutil - -// This is a set of fluent request builders for tests, which help us to -// simplify constructing and unmarshalling test objects. For example, to post -// a body and return a response, you would do something like: -// -// var body RequestBody -// var response ResponseBody -// t is *testing.T, from a unit test -// e is *echo.Echo -// response := NewRequest().Post("/path").WithJsonBody(body).Go(t, e) -// err := response.UnmarshalBodyToObject(&response) -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/labstack/echo/v4" -) - -func NewRequest() *RequestBuilder { - return &RequestBuilder{ - Headers: make(map[string]string), - } -} - -// RequestBuilder caches request settings as we build up the request. -type RequestBuilder struct { - Method string - Path string - Headers map[string]string - Body []byte - Error error - Cookies []*http.Cookie -} - -// WithMethod sets the method and path -func (r *RequestBuilder) WithMethod(method string, path string) *RequestBuilder { - r.Method = method - r.Path = path - return r -} - -func (r *RequestBuilder) Get(path string) *RequestBuilder { - return r.WithMethod("GET", path) -} - -func (r *RequestBuilder) Post(path string) *RequestBuilder { - return r.WithMethod("POST", path) -} - -func (r *RequestBuilder) Put(path string) *RequestBuilder { - return r.WithMethod("PUT", path) -} - -func (r *RequestBuilder) Patch(path string) *RequestBuilder { - return r.WithMethod("PATCH", path) -} - -func (r *RequestBuilder) Delete(path string) *RequestBuilder { - return r.WithMethod("DELETE", path) -} - -// WithHeader sets a header -func (r *RequestBuilder) WithHeader(header, value string) *RequestBuilder { - r.Headers[header] = value - return r -} - -func (r *RequestBuilder) WithJWSAuth(jws string) *RequestBuilder { - r.Headers["Authorization"] = "Bearer " + jws - return r -} - -func (r *RequestBuilder) WithHost(value string) *RequestBuilder { - return r.WithHeader("Host", value) -} - -func (r *RequestBuilder) WithContentType(value string) *RequestBuilder { - return r.WithHeader("Content-Type", value) -} - -func (r *RequestBuilder) WithJsonContentType() *RequestBuilder { - return r.WithContentType("application/json") -} - -func (r *RequestBuilder) WithAccept(value string) *RequestBuilder { - return r.WithHeader("Accept", value) -} - -func (r *RequestBuilder) WithAcceptJson() *RequestBuilder { - return r.WithAccept("application/json") -} - -// Request body operations - -func (r *RequestBuilder) WithBody(body []byte) *RequestBuilder { - r.Body = body - return r -} - -// WithJsonBody takes an object as input, marshals it to JSON, and sends it -// as the body with Content-Type: application/json -func (r *RequestBuilder) WithJsonBody(obj interface{}) *RequestBuilder { - var err error - r.Body, err = json.Marshal(obj) - if err != nil { - r.Error = fmt.Errorf("failed to marshal json object: %s", err) - } - return r.WithJsonContentType() -} - -// WithCookie sets a cookie -func (r *RequestBuilder) WithCookie(c *http.Cookie) *RequestBuilder { - r.Cookies = append(r.Cookies, c) - return r -} - -func (r *RequestBuilder) WithCookieNameValue(name, value string) *RequestBuilder { - return r.WithCookie(&http.Cookie{Name: name, Value: value}) -} - -// GoWithHTTPHandler performs the request, it takes a pointer to a testing context -// to print messages, and a http handler for request handling. -func (r *RequestBuilder) GoWithHTTPHandler(t *testing.T, handler http.Handler) *CompletedRequest { - if r.Error != nil { - // Fail the test if we had an error - t.Errorf("error constructing request: %s", r.Error) - return nil - } - var bodyReader io.Reader - if r.Body != nil { - bodyReader = bytes.NewReader(r.Body) - } - - req := httptest.NewRequest(r.Method, r.Path, bodyReader) - for h, v := range r.Headers { - req.Header.Add(h, v) - } - if host, ok := r.Headers["Host"]; ok { - req.Host = host - } - for _, c := range r.Cookies { - req.AddCookie(c) - } - - rec := httptest.NewRecorder() - handler.ServeHTTP(rec, req) - - return &CompletedRequest{ - Recorder: rec, - } -} - -// Go performs the request, it takes a pointer to a testing context -// to print messages, and a pointer to an echo context for request handling. -func (r *RequestBuilder) Go(t *testing.T, e *echo.Echo) *CompletedRequest { - return r.GoWithHTTPHandler(t, e) -} - -// CompletedRequest is the result of calling Go() on the request builder. We're wrapping the -// ResponseRecorder with some nice helper functions. -type CompletedRequest struct { - Recorder *httptest.ResponseRecorder - - // When set to true, decoders will be more strict. In the default JSON - // recorder, unknown fields will cause errors. - Strict bool -} - -func (c *CompletedRequest) DisallowUnknownFields() { - c.Strict = true -} - -// UnmarshalBodyToObject takes a destination object as input, and unmarshals the object -// in the response based on the Content-Type header. -func (c *CompletedRequest) UnmarshalBodyToObject(obj interface{}) error { - ctype := c.Recorder.Header().Get("Content-Type") - - // Content type can have an annotation after ; - contentParts := strings.Split(ctype, ";") - content := strings.TrimSpace(contentParts[0]) - handler := getHandler(content) - if handler == nil { - return fmt.Errorf("unhandled content: %s", content) - } - - return handler(ctype, c.Recorder.Body, obj, c.Strict) -} - -// UnmarshalJsonToObject assumes that the response contains JSON and unmarshals it -// into the specified object. -func (c *CompletedRequest) UnmarshalJsonToObject(obj interface{}) error { - return json.Unmarshal(c.Recorder.Body.Bytes(), obj) -} - -// Code is a shortcut for response code -func (c *CompletedRequest) Code() int { - return c.Recorder.Code -} diff --git a/pkg/testutil/response_handlers.go b/pkg/testutil/response_handlers.go deleted file mode 100644 index 75089e4601..0000000000 --- a/pkg/testutil/response_handlers.go +++ /dev/null @@ -1,44 +0,0 @@ -package testutil - -import ( - "encoding/json" - "io" - "sync" -) - -func init() { - knownHandlers = make(map[string]ResponseHandler) - - RegisterResponseHandler("application/json", jsonHandler) -} - -var ( - knownHandlersMu sync.Mutex - knownHandlers map[string]ResponseHandler -) - -type ResponseHandler func(contentType string, raw io.Reader, obj interface{}, strict bool) error - -func RegisterResponseHandler(mime string, handler ResponseHandler) { - knownHandlersMu.Lock() - defer knownHandlersMu.Unlock() - - knownHandlers[mime] = handler -} - -func getHandler(mime string) ResponseHandler { - knownHandlersMu.Lock() - defer knownHandlersMu.Unlock() - - return knownHandlers[mime] -} - -// This function assumes that the response contains JSON and unmarshals it -// into the specified object. -func jsonHandler(_ string, r io.Reader, obj interface{}, strict bool) error { - d := json.NewDecoder(r) - if strict { - d.DisallowUnknownFields() - } - return d.Decode(obj) -} diff --git a/pkg/types/date.go b/pkg/types/date.go deleted file mode 100644 index 6ad852f637..0000000000 --- a/pkg/types/date.go +++ /dev/null @@ -1,43 +0,0 @@ -package types - -import ( - "encoding/json" - "time" -) - -const DateFormat = "2006-01-02" - -type Date struct { - time.Time -} - -func (d Date) MarshalJSON() ([]byte, error) { - return json.Marshal(d.Time.Format(DateFormat)) -} - -func (d *Date) UnmarshalJSON(data []byte) error { - var dateStr string - err := json.Unmarshal(data, &dateStr) - if err != nil { - return err - } - parsed, err := time.Parse(DateFormat, dateStr) - if err != nil { - return err - } - d.Time = parsed - return nil -} - -func (d Date) String() string { - return d.Time.Format(DateFormat) -} - -func (d *Date) UnmarshalText(data []byte) error { - parsed, err := time.Parse(DateFormat, string(data)) - if err != nil { - return err - } - d.Time = parsed - return nil -} diff --git a/pkg/types/date_test.go b/pkg/types/date_test.go deleted file mode 100644 index 211776522f..0000000000 --- a/pkg/types/date_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package types - -import ( - "encoding/json" - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestDate_MarshalJSON(t *testing.T) { - testDate := time.Date(2019, 4, 1, 0, 0, 0, 0, time.UTC) - b := struct { - DateField Date `json:"date"` - }{ - DateField: Date{testDate}, - } - jsonBytes, err := json.Marshal(b) - assert.NoError(t, err) - assert.JSONEq(t, `{"date":"2019-04-01"}`, string(jsonBytes)) -} - -func TestDate_UnmarshalJSON(t *testing.T) { - testDate := time.Date(2019, 4, 1, 0, 0, 0, 0, time.UTC) - jsonStr := `{"date":"2019-04-01"}` - b := struct { - DateField Date `json:"date"` - }{} - err := json.Unmarshal([]byte(jsonStr), &b) - assert.NoError(t, err) - assert.Equal(t, testDate, b.DateField.Time) -} - -func TestDate_Stringer(t *testing.T) { - t.Run("nil date", func(t *testing.T) { - var d *Date - assert.Equal(t, "", fmt.Sprintf("%v", d)) - }) - - t.Run("ptr date", func(t *testing.T) { - d := &Date{ - Time: time.Date(2019, 4, 1, 0, 0, 0, 0, time.UTC), - } - assert.Equal(t, "2019-04-01", fmt.Sprintf("%v", d)) - }) - - t.Run("value date", func(t *testing.T) { - d := Date{ - Time: time.Date(2019, 4, 1, 0, 0, 0, 0, time.UTC), - } - assert.Equal(t, "2019-04-01", fmt.Sprintf("%v", d)) - }) -} - -func TestDate_UnmarshalText(t *testing.T) { - testDate := time.Date(2022, 6, 14, 0, 0, 0, 0, time.UTC) - value := []byte("2022-06-14") - - date := Date{} - err := date.UnmarshalText(value) - - assert.NoError(t, err) - assert.Equal(t, testDate, date.Time) -} diff --git a/pkg/types/email.go b/pkg/types/email.go deleted file mode 100644 index 00a4cf6b93..0000000000 --- a/pkg/types/email.go +++ /dev/null @@ -1,27 +0,0 @@ -package types - -import ( - "encoding/json" - "errors" -) - -type Email string - -func (e Email) MarshalJSON() ([]byte, error) { - if !emailRegex.MatchString(string(e)) { - return nil, errors.New("email: failed to pass regex validation") - } - return json.Marshal(string(e)) -} - -func (e *Email) UnmarshalJSON(data []byte) error { - var s string - if err := json.Unmarshal(data, &s); err != nil { - return err - } - if !emailRegex.MatchString(s) { - return errors.New("email: failed to pass regex validation") - } - *e = Email(s) - return nil -} diff --git a/pkg/types/email_test.go b/pkg/types/email_test.go deleted file mode 100644 index 69fb975f23..0000000000 --- a/pkg/types/email_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package types - -import ( - "encoding/json" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestEmail_MarshalJSON(t *testing.T) { - testEmail := "gaben@valvesoftware.com" - b := struct { - EmailField Email `json:"email"` - }{ - EmailField: Email(testEmail), - } - jsonBytes, err := json.Marshal(b) - assert.NoError(t, err) - assert.JSONEq(t, `{"email":"gaben@valvesoftware.com"}`, string(jsonBytes)) -} - -func TestEmail_UnmarshalJSON(t *testing.T) { - testEmail := Email("gaben@valvesoftware.com") - jsonStr := `{"email":"gaben@valvesoftware.com"}` - b := struct { - EmailField Email `json:"email"` - }{} - err := json.Unmarshal([]byte(jsonStr), &b) - assert.NoError(t, err) - assert.Equal(t, testEmail, b.EmailField) -} diff --git a/pkg/types/file.go b/pkg/types/file.go deleted file mode 100644 index 7b26a0d7db..0000000000 --- a/pkg/types/file.go +++ /dev/null @@ -1,71 +0,0 @@ -package types - -import ( - "bytes" - "encoding/json" - "io" - "mime/multipart" -) - -type File struct { - multipart *multipart.FileHeader - data []byte - filename string -} - -func (file *File) InitFromMultipart(header *multipart.FileHeader) { - file.multipart = header - file.data = nil - file.filename = "" -} - -func (file *File) InitFromBytes(data []byte, filename string) { - file.data = data - file.filename = filename - file.multipart = nil -} - -func (file File) MarshalJSON() ([]byte, error) { - b, err := file.Bytes() - if err != nil { - return nil, err - } - return json.Marshal(b) -} - -func (file *File) UnmarshalJSON(data []byte) error { - return json.Unmarshal(data, &file.data) -} - -func (file File) Bytes() ([]byte, error) { - if file.multipart != nil { - f, err := file.multipart.Open() - if err != nil { - return nil, err - } - defer func() { _ = f.Close() }() - return io.ReadAll(f) - } - return file.data, nil -} - -func (file File) Reader() (io.ReadCloser, error) { - if file.multipart != nil { - return file.multipart.Open() - } - return io.NopCloser(bytes.NewReader(file.data)), nil -} - -func (file File) Filename() string { - if file.multipart != nil { - return file.multipart.Filename - } - return file.filename -} - -func (file File) FileSize() int64 { - if file.multipart != nil { - return file.multipart.Size - } - return int64(len(file.data)) -} diff --git a/pkg/types/file_test.go b/pkg/types/file_test.go deleted file mode 100644 index fb4ce98ae1..0000000000 --- a/pkg/types/file_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package types - -import ( - "encoding/json" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var _ json.Marshaler = (*File)(nil) -var _ json.Unmarshaler = (*File)(nil) - -func TestFileJSON(t *testing.T) { - type Object struct { - BinaryField File `json:"binary_field"` - } - - // Check whether we encode JSON properly. - var o Object - o.BinaryField.InitFromBytes([]byte("hello"), "") - buf, err := json.Marshal(o) - require.NoError(t, err) - t.Log(string(buf)) - - // Decode the same object back into File, ensure result is correct. - var o2 Object - err = json.Unmarshal(buf, &o2) - require.NoError(t, err) - o2Bytes, err := o2.BinaryField.Bytes() - require.NoError(t, err) - assert.Equal(t, []byte("hello"), o2Bytes) - - // Ensure it also works via pointer. - type Object2 struct { - BinaryFieldPtr *File `json:"binary_field"` - } - - var o3 Object2 - var f File - f.InitFromBytes([]byte("hello"), "") - o3.BinaryFieldPtr = &f - buf, err = json.Marshal(o) - require.NoError(t, err) - t.Log(string(buf)) - - var o4 Object2 - err = json.Unmarshal(buf, &o4) - require.NoError(t, err) - o4Bytes, err := o4.BinaryFieldPtr.Bytes() - require.NoError(t, err) - assert.Equal(t, []byte("hello"), o4Bytes) - -} diff --git a/pkg/types/regexes.go b/pkg/types/regexes.go deleted file mode 100644 index 94f17df58e..0000000000 --- a/pkg/types/regexes.go +++ /dev/null @@ -1,11 +0,0 @@ -package types - -import "regexp" - -const ( - emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" -) - -var ( - emailRegex = regexp.MustCompile(emailRegexString) -) diff --git a/pkg/types/uuid.go b/pkg/types/uuid.go deleted file mode 100644 index 1c11f38ba0..0000000000 --- a/pkg/types/uuid.go +++ /dev/null @@ -1,7 +0,0 @@ -package types - -import ( - "github.com/google/uuid" -) - -type UUID = uuid.UUID diff --git a/pkg/types/uuid_test.go b/pkg/types/uuid_test.go deleted file mode 100644 index bb62040b6c..0000000000 --- a/pkg/types/uuid_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package types - -import ( - "encoding/json" - "testing" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" -) - -func TestUUID_MarshalJSON_Zero(t *testing.T) { - var testUUID UUID - b := struct { - UUIDField UUID `json:"uuid"` - }{ - UUIDField: testUUID, - } - marshaled, err := json.Marshal(b) - assert.NoError(t, err) - assert.JSONEq(t, `{"uuid":"00000000-0000-0000-0000-000000000000"}`, string(marshaled)) -} - -func TestUUID_MarshalJSON_Pass(t *testing.T) { - testUUID := uuid.MustParse("9cb14230-b640-11ec-b909-0242ac120002") - b := struct { - UUIDField UUID `json:"uuid"` - }{ - UUIDField: testUUID, - } - jsonBytes, err := json.Marshal(b) - assert.NoError(t, err) - assert.JSONEq(t, `{"uuid":"9cb14230-b640-11ec-b909-0242ac120002"}`, string(jsonBytes)) -} - -func TestUUID_UnmarshalJSON_Fail(t *testing.T) { - jsonStr := `{"uuid":"this-is-not-a-uuid"}` - b := struct { - UUIDField UUID `json:"uuid"` - }{} - err := json.Unmarshal([]byte(jsonStr), &b) - assert.Error(t, err) -} - -func TestUUID_UnmarshalJSON_Pass(t *testing.T) { - testUUID := uuid.MustParse("9cb14230-b640-11ec-b909-0242ac120002") - jsonStr := `{"uuid":"9cb14230-b640-11ec-b909-0242ac120002"}` - b := struct { - UUIDField UUID `json:"uuid"` - }{} - err := json.Unmarshal([]byte(jsonStr), &b) - assert.NoError(t, err) - assert.Equal(t, testUUID, b.UUIDField) -} diff --git a/pkg/util/inputmapping.go b/pkg/util/inputmapping.go index fb2ac756aa..613434a980 100644 --- a/pkg/util/inputmapping.go +++ b/pkg/util/inputmapping.go @@ -5,7 +5,7 @@ import ( "strings" ) -// The input mapping is experessed on the command line as `key1:value1,key2:value2,...` +// The input mapping is expressed on the command line as `key1:value1,key2:value2,...` // We parse it here, but need to keep in mind that keys or values may contain // commas and colons. We will allow escaping those using double quotes, so // when passing in "key1":"value1", we will not look inside the quoted sections. @@ -47,7 +47,7 @@ func ParseCommandLineList(input string) []string { return args } -// This function splits a string along the specifed separator, but it +// splitString splits a string along the specified separator, but it // ignores anything between double quotes for splitting. We do simple // inside/outside quote counting. Quotes are not stripped from output. func splitString(s string, sep rune) []string { @@ -73,7 +73,7 @@ func splitString(s string, sep rune) []string { part = "" continue } - part = part + string(c) + part += string(c) } return append(parts, part) } diff --git a/pkg/util/isjson.go b/pkg/util/isjson.go index 63f63729b8..5013ba7a74 100644 --- a/pkg/util/isjson.go +++ b/pkg/util/isjson.go @@ -1,7 +1,14 @@ package util -import "strings" +import ( + "mime" + "strings" +) func IsMediaTypeJson(mediaType string) bool { - return mediaType == "application/json" || strings.HasSuffix(mediaType, "+json") + parsed, _, err := mime.ParseMediaType(mediaType) + if err != nil { + return false + } + return parsed == "application/json" || strings.HasSuffix(parsed, "+json") } diff --git a/pkg/util/isjson_test.go b/pkg/util/isjson_test.go index be3cd2463c..8271aef49b 100644 --- a/pkg/util/isjson_test.go +++ b/pkg/util/isjson_test.go @@ -41,6 +41,18 @@ func TestIsMediaTypeJson(t *testing.T) { mediaType: "application/vnd.api+json", want: true, }, + { + // NOTE that this _technically_ isn't a standard extension to JSON https://www.iana.org/assignments/media-types/application/json but due to the fact that several APIs do use it, we should support it + name: "When MediaType is application/json;v=1, returns true", + mediaType: "application/json;v=1", + want: true, + }, + { + // NOTE that this _technically_ isn't a standard extension to JSON https://www.iana.org/assignments/media-types/application/json but due to the fact that several APIs do use it, we should support it + name: "When MediaType is application/json;version=1, returns true", + mediaType: "application/json;version=1", + want: true, + }, } for _, test := range suite { t.Run(test.name, func(t *testing.T) { diff --git a/pkg/util/loader.go b/pkg/util/loader.go index 9efbb8f38e..7eea07b156 100644 --- a/pkg/util/loader.go +++ b/pkg/util/loader.go @@ -1,9 +1,15 @@ package util import ( + "bytes" + "fmt" "net/url" + "path/filepath" + "strings" "github.com/getkin/kin-openapi/openapi3" + "github.com/speakeasy-api/openapi-overlay/pkg/loader" + "gopkg.in/yaml.v3" ) func LoadSwagger(filePath string) (swagger *openapi3.T, err error) { @@ -18,3 +24,81 @@ func LoadSwagger(filePath string) (swagger *openapi3.T, err error) { return loader.LoadFromFile(filePath) } } + +// Deprecated: In kin-openapi v0.126.0 (https://github.com/getkin/kin-openapi/tree/v0.126.0?tab=readme-ov-file#v01260) the Circular Reference Counter functionality was removed, instead resolving all references with backtracking, to avoid needing to provide a limit to reference counts. +// +// This is now identital in method as `LoadSwagger`. +func LoadSwaggerWithCircularReferenceCount(filePath string, _ int) (swagger *openapi3.T, err error) { + return LoadSwagger(filePath) +} + +type LoadSwaggerWithOverlayOpts struct { + Path string + Strict bool +} + +func LoadSwaggerWithOverlay(filePath string, opts LoadSwaggerWithOverlayOpts) (swagger *openapi3.T, err error) { + spec, err := LoadSwagger(filePath) + if err != nil { + return nil, fmt.Errorf("failed to load OpenAPI specification: %w", err) + } + + if opts.Path == "" { + return spec, nil + } + + // parse out the yaml.Node, which is required by the overlay library + buf := &bytes.Buffer{} + enc := yaml.NewEncoder(buf) + // set to 2 to work around https://github.com/yaml/go-yaml/issues/76 + enc.SetIndent(2) + err = enc.Encode(spec) + if err != nil { + return nil, fmt.Errorf("failed to marshal spec from %#v as YAML: %w", filePath, err) + } + + var node yaml.Node + err = yaml.NewDecoder(buf).Decode(&node) + if err != nil { + return nil, fmt.Errorf("failed to parse spec from %#v: %w", filePath, err) + } + + overlay, err := loader.LoadOverlay(opts.Path) + if err != nil { + return nil, fmt.Errorf("failed to load Overlay from %#v: %v", opts.Path, err) + } + + err = overlay.Validate() + if err != nil { + return nil, fmt.Errorf("the Overlay in %#v was not valid: %v", opts.Path, err) + } + + if opts.Strict { + err, vs := overlay.ApplyToStrict(&node) + if err != nil { + return nil, fmt.Errorf("failed to apply Overlay %#v to specification %#v: %v\nAdditionally, the following validation errors were found:\n- %s", opts.Path, filePath, err, strings.Join(vs, "\n- ")) + } + } else { + err = overlay.ApplyTo(&node) + if err != nil { + return nil, fmt.Errorf("failed to apply Overlay %#v to specification %#v: %v", opts.Path, filePath, err) + } + } + + b, err := yaml.Marshal(&node) + if err != nil { + return nil, fmt.Errorf("failed to serialize Overlay'd specification %#v: %v", opts.Path, err) + } + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + + swagger, err = loader.LoadFromDataWithPath(b, &url.URL{ + Path: filepath.ToSlash(filePath), + }) + if err != nil { + return nil, fmt.Errorf("failed to serialize Overlay'd specification %#v: %v", opts.Path, err) + } + + return swagger, nil +} diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000000..8ac4d6e63c --- /dev/null +++ b/renovate.json @@ -0,0 +1,86 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "local>oapi-codegen/renovate-config" + ], + "gomod": { + "ignorePaths": [] + }, + "vulnerabilityAlerts": { + "commitMessageSuffix": "{{#if isGroup }}{{ else }} ({{#if packageFileDir}}{{packageFileDir}}{{else}}{{packageFile}}{{/if}}){{/if}} [SECURITY]" + }, + "packageRules": [ + { + "description": "Ensure that each directory has their own set of dependency updates, split by the parent directory of the package file (`packageFileDir`). Groups will be unaffected.", + "matchFileNames": [ + "**/*" + ], + "additionalBranchPrefix": "{{#if isGroup }}{{ else }}{{#if packageFileDir}}{{packageFileDir}}/{{else}}{{packageFile}}/{{/if}}{{/if}}", + "commitMessageSuffix": "{{#if isGroup }}{{ else }} ({{#if packageFileDir}}{{packageFileDir}}{{else}}{{packageFile}}{{/if}}){{/if}}" + }, + { + "description": "Label example/test code separately", + "matchFileNames": [ + "internal/test/**/*", + "examples/**/*" + ], + "labels": [ + "dependencies-test-only" + ] + }, + { + "description": "Don't attempt to bump dependencies if they're only used in example code, but allow manually forcing them via Dependency Dashboard", + "matchFileNames": [ + "internal/test/**/*", + "examples/**/*" + ], + "dependencyDashboardApproval": true + }, + { + "description": "Don't attempt to bump `replace` statements for internal modules", + "matchDepNames": [ + "github.com/oapi-codegen/oapi-codegen/v2" + ], + "matchCurrentVersion": "v2.0.0-00010101000000-000000000000", + "enabled": false + }, + { + "description": "Don't attempt to bump `replace` statements for internal modules", + "matchDepNames": [ + "github.com/oapi-codegen/oapi-codegen/v2/internal/test" + ], + "matchCurrentVersion": "v0.0.0-00010101000000-000000000000", + "enabled": false + }, + { + "description": "", + "matchFileNames": [ + "**/*/go.mod", + "**/*.go.sum" + ], + "postUpgradeTasks": { + "commands": [ + "make tidy" + ], + "fileFilters": [ + "**/*/go.mod", + "**/*/go.sum" + ], + "executionMode": "branch" + } + } + ], + "customManagers": [ + { + "customType": "regex", + "managerFilePatterns": [ + "README.md" + ], + "matchStrings": [ + "# yaml-language-server: \\$schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/(?[^/]+)/configuration-schema.json" + ], + "depNameTemplate": "github.com/oapi-codegen/oapi-codegen/v2", + "datasourceTemplate": "go" + } + ] +} diff --git a/tools/tools.go b/tools/tools.go deleted file mode 100644 index f3c3d2fcd7..0000000000 --- a/tools/tools.go +++ /dev/null @@ -1,7 +0,0 @@ -// +build tools - -package tools - -import ( - _ "github.com/matryer/moq" -)