From 457da082452d9c924f6426c0c8d5ea66b3b93881 Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Mon, 4 Mar 2024 21:30:39 +0000 Subject: [PATCH 01/20] WIP --- CONTRIBUTING.md | 75 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 50 +++++++++++---------------------- 2 files changed, 91 insertions(+), 34 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..629cad89e3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,75 @@ +## 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. + + +``` +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. + +If you do find a case where oapi-codegen is broken, and would like to submit a PR, +we are very grateful, and will happily look at it. + +Since most commits affect generated code, before sending your PR, please +ensure that all boilerplate has been regenerated. You can do this from the top level +of the repository by running: + + make generate + +I realize that our code isn't entirely idiomatic with respect to comments, and +variable naming and initialisms, especially the generated code, but I'm reluctant +to merge PR's which change this, due to the breakage they will cause for others. If +you rename anything under `/pkg` or change the names of variables in generated +code, you will break other people's code. It's safe to rename internal names. + + +``` + +This guide is a starting point, and we'd absolutely **??**. We've managed to go ~4 years without a substantial guide like this, and would love to keep improving it for the best of the community. + +### Raising a bug + +If you believe **??**. + +This may get converted into a feature request if needed. + +### Asking a question + +We'd prefer that questions about "how do I use (this feature)" or **??** get asked using [GitHub Discussions](https://github.com/deepmap/oapi-codegen/discussions) which allow **??** + +### Making changes that tweak generated code + +If you are generating **??**. + +These provide a **??**, and as they are checked in to source code, allow us to **??**. + +Now, please note that significant changes to generated code are likley to **??**, especially in cases where there would be a breaking change issued to **??**. + +### Feature enhancements + +**Should introduce a flag if possible** + +### Minimal reproductions + +### Before you raise a PR + +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 make our **??**. + +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 diff --git a/README.md b/README.md index 9e1d83d636..344549e066 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,19 @@ -## OpenAPI Client and Server Code Generator +# `oapi-codegen` + +`oapi-codegen` is a library, and command-line tool, to convert OpenAPI API specifications to Go code, be it server-side implementations, API clients, or simply models. + +`oapi-codegen` aims to reduce some of the tedious boilerplate that can be found when building or interacting with APIs, and focusses on: + +- 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 ⚠️ 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. +## OpenAPI Client and Server Code Generator + 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) @@ -29,34 +39,6 @@ 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. -## Contributing - -I would like to pre-emptively extend my 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. - -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. - -If you do find a case where oapi-codegen is broken, and would like to submit a PR, -we are very grateful, and will happily look at it. - -Since most commits affect generated code, before sending your PR, please -ensure that all boilerplate has been regenerated. You can do this from the top level -of the repository by running: - - make generate - -I realize that our code isn't entirely idiomatic with respect to comments, and -variable naming and initialisms, especially the generated code, but I'm reluctant -to merge PR's which change this, due to the breakage they will cause for others. If -you rename anything under `/pkg` or change the names of variables in generated -code, you will break other people's code. It's safe to rename internal names. - ## Overview We're going to use the OpenAPI example of the @@ -713,17 +695,17 @@ which help you to use the various OpenAPI 3 Authentication mechanism. - `x-go-type-skip-optional-pointer`: specifies if the Go type should or should not be a pointer when the property is optional. If set to true, the type will not be a pointer if the field is optional or nullable. If set to false, the type will be a pointer. - + ```yaml properties: field: type: string x-go-type-skip-optional-pointer: true ``` - + In the example above, the `field` field will be of type `string` instead of `*string`. This is useful when you want to handle the case of an empty string differently than a null value. - + - `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 @@ -871,7 +853,7 @@ which help you to use the various OpenAPI 3 Authentication mechanism. End *openapi_types.Date `json:"end,omitempty"` } ``` - + ## Using `oapi-codegen` The default options for `oapi-codegen` will generate everything; client, server, @@ -909,7 +891,7 @@ 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 or operationId in the openapi definition. -Use either `-include-tags`, `include-operation-ids` or `-exclude-tags`, `-exclude-operation-ids` +Use either `-include-tags`, `include-operation-ids` or `-exclude-tags`, `-exclude-operation-ids` followed by a comma-separated list of tags or operation ids. 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 From 423defeaddc4492c8e8a1e0187717cc1d429a34c Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Sun, 24 Mar 2024 17:53:08 +0000 Subject: [PATCH 02/20] sq --- README.md | 161 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/README.md b/README.md index 344549e066..2d6614ed47 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ release version. ## OpenAPI Client and Server Code Generator +``` 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) @@ -38,6 +39,166 @@ 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. +``` + +## Install + +It is recommended to follow [the `tools.go` pattern](https://www.jvt.me/posts/2022/06/15/go-tools-dependency-management/) for managing the dependency of `oapi-codegen` alongside your core application. + +This would give you a `tools/tools.go`: + +```go +//go:build tools +// +build tools + +package main + +import ( + _ "github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen" +) +``` + +Then, each **??** + +Alternatively, you can install it as a binary with: + +```sh +$ go install github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen@latest +$ oapi-codegen -version +``` + +## Usage + +`oapi-codegen` is largely **??**. + +For full https://pkg.go.dev/github.com/deepmap/oapi-codegen/v2/pkg/codegen#Configuration + +## Features + +`oapi-codegen` supports: + +- Generating server-side boilerplate for [a number of servers] +- Generating client API boilerplate +- Generating the types +- Splitting **??** + - Also described as ["external refs"] or "Import Mappings" in our documentation + +## Key design decisions + +- Bulk processing and parsing of OpenAPI document in Go +- Resulting output is using Go's `text/template`s, which are user-overridable +- Idiomatic Go +- Single-file output +- Support multiple OpenAPI files by having a package-per-file +- **??** + +### 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. + +``` +-------- +``` + +Now you've generated this, you need to implement **??**. + +```go + +``` + +To provide you a fully Test Driven Development style test harness, you could use a tool such as **??** to **??**. + +#### Supported Servers + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Server name + +Configuration **??** +
+ + +
+ + +
+ + +
+ + +
+ +### Generating API clients + +### 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. + +### Generating Nullable types + +Opt-in, **??** + +### OpenAPI extensions + +As well as inbuilt OpenAPI, we also support the following OpenAPI extensions. + +#### + +### Custom code generation + +It is possible to extend **??** using the templates **??**. + +Alternatively, you are able to use the underlying code generation as a package, which [will be documented in the future](https://github.com/deepmap/oapi-codegen/issues/1487). + +## Examples + +The [examples directory] contains some **??**, including how you'd take the Petstore API and **??**. + + + +### Blog posts + +The are a number of **??** + +- TODO +https://www.jvt.me/posts/2022/07/12/go-openapi-server/ + +Got one to add? Please raise a PR! + +-------- +-------- +-------- +-------- +-------- +-------- +-------- ## Overview From 6e8a1dd05f7472ee7d26b53b0dad430a0918462d Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Sun, 24 Mar 2024 18:27:32 +0000 Subject: [PATCH 03/20] sq --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 2d6614ed47..06b2769d13 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ unreleased changes. Please ensure you're looking at the README for the latest release version. +Does not 2.0 + ## OpenAPI Client and Server Code Generator ``` @@ -175,6 +177,26 @@ As well as inbuilt OpenAPI, we also support the following OpenAPI extensions. It is possible to extend **??** using the templates **??**. +#### Local paths + +#### HTTPS paths + +It is also possible to use HTTPS URLs. + +> [!WARNING] +> Although possible, this does lead to **??**. It's recommended to vendor (copy) the OpenAPI spec into your **??** + +To use it, **??** + +```yaml +TODO +``` + +> [!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. + +#### 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/deepmap/oapi-codegen/issues/1487). ## Examples From 1cf390f6ab6050d9065ce304b2cb648f718cc2d4 Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Mon, 25 Mar 2024 09:34:08 +0000 Subject: [PATCH 04/20] sq --- CONTRIBUTING.md | 3 ++ README.md | 125 +++++++++++++++++++++++++++++++----------------- 2 files changed, 84 insertions(+), 44 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 629cad89e3..6f2a02c429 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,6 +55,9 @@ Now, please note that significant changes to generated code are likley to **??** **Should introduce a flag if possible** +**Opt-in by default?** + + ### Minimal reproductions ### Before you raise a PR diff --git a/README.md b/README.md index 06b2769d13..7cccc42b84 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # `oapi-codegen` -`oapi-codegen` is a library, and command-line tool, to convert OpenAPI API specifications to Go code, be it server-side implementations, API clients, or simply models. +`oapi-codegen` is a command-line tool and library, to convert OpenAPI API specifications to Go code, be it server-side implementations, API clients, or simply HTTP models. `oapi-codegen` aims to reduce some of the tedious boilerplate that can be found when building or interacting with APIs, and focusses on: @@ -8,40 +8,9 @@ - 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 -⚠️ 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. +You can read more about our [Design Decisions](#design-decisions) below. -Does not 2.0 - -## OpenAPI Client and Server Code Generator - -``` -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. - -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. [Chi](https://github.com/go-chi/chi), [Gin](https://github.com/gin-gonic/gin), -[gorilla/mux](https://github.com/gorilla/mux), [Fiber](https://github.com/gofiber/fiber), -[Iris](https://github.com/kataras/iris), and [1.22+ net/http](https://pkg.go.dev/net/http) -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. - -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. -``` +⚠️ 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. ## Install @@ -60,7 +29,11 @@ import ( ) ``` -Then, each **??** +Then, each invocation of `oapi-codegen` would be used like so: + +```go +//go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../../api.yaml +``` Alternatively, you can install it as a binary with: @@ -69,9 +42,15 @@ $ go install github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen@latest $ oapi-codegen -version ``` +Which then means you can invoke it like so: + +```go +//go:generate go run oapi-codegen --config=config.yaml ../../api.yaml +``` + ## Usage -`oapi-codegen` is largely **??**. +`oapi-codegen` is largely configured using a YAML configuration file, to simplify the number of flags that users need to remember. For full https://pkg.go.dev/github.com/deepmap/oapi-codegen/v2/pkg/codegen#Configuration @@ -79,7 +58,7 @@ For full https://pkg.go.dev/github.com/deepmap/oapi-codegen/v2/pkg/codegen#Confi `oapi-codegen` supports: -- Generating server-side boilerplate for [a number of servers] +- Generating server-side boilerplate for [a number of servers] TODO - Generating client API boilerplate - Generating the types - Splitting **??** @@ -93,6 +72,18 @@ For full https://pkg.go.dev/github.com/deepmap/oapi-codegen/v2/pkg/codegen#Confi - Single-file output - Support multiple OpenAPI files by having a package-per-file - **??** +- Support of OpenAPI 3.x + - OpenAPI 3.1 support is [awaiting upstream support](https://github.com/deepmap/oapi-codegen/issues/373) + - Note that this does not include OpenAPI 2.0 (aka Swagger) + +``` +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. +``` ### Generating server-side boilerplate @@ -116,42 +107,88 @@ To provide you a fully Test Driven Development style test harness, you could use -Server name +Server -Configuration **??** +generate flag to enable code generation + +[Chi](https://github.com/go-chi/chi) + - +chi-server + +[Echo](https://github.com/labstack/echo) + - +echo-server + +[Fiber](https://github.com/gofiber/fiber) + + + +fiber-server + + + + + + + +[Gin](https://github.com/gin-gonic/gin) + - +gin-server + +[gorilla/mux](https://github.com/gorilla/mux) + + + +gorilla-server + + + + + + +[Iris](https://github.com/kataras/iris) + + + +iris-server + + + + + + +[1.22+ `net/http`](https://pkg.go.dev/net/http) + - +std-http-server @@ -225,7 +262,7 @@ Got one to add? Please raise a PR! ## Overview 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) +[Expanded Petstore](https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v3.0/petstore-expanded.yaml) in the descriptions below, please have a look at it. In order to create a Go server to serve this exact schema, you would have to From aef0753220867f5075aa079d7208cdf3d4c07ac3 Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Mon, 25 Mar 2024 09:36:21 +0000 Subject: [PATCH 05/20] sq --- README.md | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 7cccc42b84..7ccfa11616 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ - 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 -You can read more about our [Design Decisions](#design-decisions) below. +You can read more about our [Key Design Decisions](#key-design-decisions) below. ⚠️ 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. @@ -110,7 +110,7 @@ To provide you a fully Test Driven Development style test harness, you could use Server -generate flag to enable code generation +generate flag to
enable code generation @@ -1168,16 +1168,6 @@ This tells us that in order to resolve references generated from `some_spec.yaml need to import `github.com/deepmap/some-package`. You may specify multiple mappings by comma separating them in the form `key1:value1,key2:value2`. -## What's missing or incomplete - -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: - -- `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. - ## Making changes to code generation The code generator uses a tool to inline all the template definitions into From 42a392e4ac1ccd2019479c16c3ca036e8da51d78 Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Mon, 25 Mar 2024 09:36:33 +0000 Subject: [PATCH 06/20] DROPME --- .github/workflows/ci.yml | 31 --------------------------- .github/workflows/generate.yml | 31 --------------------------- .github/workflows/lint.yml | 28 ------------------------ .github/workflows/release-drafter.yml | 25 --------------------- .github/workflows/tidy.yml | 31 --------------------------- 5 files changed, 146 deletions(-) delete mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/generate.yml delete mode 100644 .github/workflows/lint.yml delete mode 100644 .github/workflows/release-drafter.yml delete mode 100644 .github/workflows/tidy.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index cefafa4ad2..0000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Build project -on: [ push, pull_request ] -jobs: - build: - name: Build - runs-on: ubuntu-latest - strategy: - fail-fast: false - # perform matrix testing to give us an earlier insight into issues with different versions of supported major versions of Go - matrix: - version: - - "1.20" - - "1.21" - - "1.22" - steps: - - name: Check out source code - uses: actions/checkout@v3 - - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: ${{ matrix.version }} - - - name: Test - run: make test - 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 - - - name: Build - run: go build ./cmd/oapi-codegen diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml deleted file mode 100644 index a7ebeed22b..0000000000 --- a/.github/workflows/generate.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Ensure generated files are up-to-date -on: [ push, pull_request ] -jobs: - build: - name: Build - runs-on: ubuntu-latest - strategy: - fail-fast: false - # perform matrix testing to give us an earlier insight into issues with different versions of supported major versions of Go - matrix: - version: - - "1.20" - - "1.21" - - "1.22" - steps: - - name: Check out source code - uses: actions/checkout@v3 - - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: ${{ matrix.version }} - - - name: Run `make generate` - run: make generate - 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 - - - name: Check for no untracked files - run: git status && git diff-index --exit-code -p HEAD -- diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 991dcf2ab2..0000000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Lint project -on: [push, pull_request] -jobs: - build: - name: Build - runs-on: ubuntu-latest - strategy: - fail-fast: false - # perform matrix testing to give us an earlier insight into issues with different versions of supported major versions of Go - matrix: - version: - - "1.20" - - "1.21" - - "1.22" - steps: - - name: Check out source code - uses: actions/checkout@v3 - - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: ${{ matrix.version }} - - - name: Run `make lint-ci` - run: make lint-ci - 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/release-drafter.yml b/.github/workflows/release-drafter.yml deleted file mode 100644 index 1890eb4a5b..0000000000 --- a/.github/workflows/release-drafter.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Release Drafter - -on: - push: - branches: - - master - 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@v6 - with: - name: next - tag: next - version: next - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tidy.yml b/.github/workflows/tidy.yml deleted file mode 100644 index 60a15c6e59..0000000000 --- a/.github/workflows/tidy.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Ensure `go mod tidy` has been run -on: [ push, pull_request ] -jobs: - build: - name: Build - runs-on: ubuntu-latest - strategy: - fail-fast: false - # perform matrix testing to give us an earlier insight into issues with different versions of supported major versions of Go - matrix: - version: - - "1.20" - - "1.21" - - "1.22" - steps: - - name: Check out source code - uses: actions/checkout@v3 - - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: ${{ matrix.version }} - - - name: Install `tidied` - run: go install gitlab.com/jamietanna/tidied@latest - - - name: Check for no untracked files - run: make tidy-ci - 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 From cb4a57c09085a185a7ae19b415acce34543b15a9 Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Mon, 25 Mar 2024 09:36:46 +0000 Subject: [PATCH 07/20] sq --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ccfa11616..e75aafda08 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ To provide you a fully Test Driven Development style test harness, you could use Server -generate flag to
enable code generation +generate flag to enable
code generation From c7b500f672864f4238da5a5b8332941378190ae0 Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Mon, 25 Mar 2024 09:38:09 +0000 Subject: [PATCH 08/20] sq --- README.md | 48 +++++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index e75aafda08..5f1cab7e5b 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,9 @@ Server generate flag to enable
code generation + +Example usage + @@ -123,6 +126,31 @@ Server chi-server + +
+ +Chi + +Code generated using `-generate chi-server`. + +```go +type PetStoreImpl struct {} +func (*PetStoreImpl) GetPets(w http.ResponseWriter, r *http.Request) { + // Implement me +} + +func SetupHandler() { + var myApi PetStoreImpl + + r := chi.NewRouter() + r.Mount("/", Handler(&myApi)) +} +``` + + + +
+ @@ -415,25 +443,7 @@ func SetupHandler() { -
Chi - -Code generated using `-generate chi-server`. - -```go -type PetStoreImpl struct {} -func (*PetStoreImpl) GetPets(w http.ResponseWriter, r *http.Request) { - // Implement me -} - -func SetupHandler() { - var myApi PetStoreImpl - - r := chi.NewRouter() - r.Mount("/", Handler(&myApi)) -} -``` - -
+
Gin From a8cc3f3c7489e106308389d061c2849018a4ca6b Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Mon, 25 Mar 2024 09:39:56 +0000 Subject: [PATCH 09/20] sq --- README.md | 378 +++++++++++++++++++++++------------------------------- 1 file changed, 162 insertions(+), 216 deletions(-) diff --git a/README.md b/README.md index 5f1cab7e5b..7425f4d011 100644 --- a/README.md +++ b/README.md @@ -147,8 +147,6 @@ func SetupHandler() { } ``` - -
@@ -162,6 +160,43 @@ func SetupHandler() { echo-server + +
Echo + +Code generated using `-generate server`. + +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) +} +``` + +The wrapper functions referenced above contain generated code which pulls +parameters off the `Echo` request context, and unmarshals them into Go objects. + +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) + ... +} +``` + +
+ @@ -185,6 +220,54 @@ func SetupHandler() { gin-server + +
Gin + +Code generated using `-generate gin`. + +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, + } + + 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 +import ( + "github.com/gin-gonic/gin" + "github.com/deepmap/oapi-codegen/examples/petstore-expanded/gin/api" + middleware "github.com/oapi-codegen/gin-middleware" +) + +type PetStoreImpl struct {} +func (*PetStoreImpl) GetPets(w http.ResponseWriter, r *http.Request) { + // Implement me +} + +func SetupHandler() { + var myApi PetStoreImpl + + r := gin.Default() + r.Use(middleware.OapiRequestValidator(swagger)) + r = api.RegisterHandlers(r, petStore) +} +``` + +
+ @@ -207,6 +290,59 @@ func SetupHandler() { iris-server + + + +
Iris + +Code generated using `-generate iris`. + +The usage of `iris` is out of scope of this doc, but once you have an +iris 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 *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() +} +``` + +```go +import ( + "github.com/kataras/iris/v12" + "github.com/deepmap/oapi-codegen/examples/petstore-expanded/iris/api" + middleware "github.com/oapi-codegen/iris-middleware" +) +``` + +The wrapper functions referenced above contain generated code which pulls +parameters off the `Iris` request context, and unmarshals them into Go objects. + +You would register the generated handlers as follows: + +```go +func SetupHandler() { + var myApi PetStoreImpl + + i := iris.Default() + i.Use(middleware.OapiRequestValidator(swagger)) + api.RegisterHandlers(r, &myApi) +} +``` + +
+ @@ -218,6 +354,30 @@ func SetupHandler() { std-http-server + + + +
net/http + +[Chi](https://github.com/go-chi/chi) is 100% compatible with `net/http` allowing the following with code generated using `-generate chi-server`. + +```go +type PetStoreImpl struct {} +func (*PetStoreImpl) GetPets(w http.ResponseWriter, r *http.Request) { + // Implement me +} + +func SetupHandler() { + var myApi PetStoreImpl + + http.Handle("/", Handler(&myApi)) +} +``` + +Alternatively, [Gorilla](https://github.com/gorilla/mux) is also 100% compatible with `net/http` and can be generated with `-generate gorilla`. + +
+ @@ -407,224 +567,10 @@ type FindPetsParams struct { 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` -
Echo -Code generated using `-generate server`. -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) -} -``` - -The wrapper functions referenced above contain generated code which pulls -parameters off the `Echo` request context, and unmarshals them into Go objects. - -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) - ... -} -``` - -
- -
- -
Gin - -Code generated using `-generate gin`. - -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, - } - - 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 -import ( - "github.com/gin-gonic/gin" - "github.com/deepmap/oapi-codegen/examples/petstore-expanded/gin/api" - middleware "github.com/oapi-codegen/gin-middleware" -) - -type PetStoreImpl struct {} -func (*PetStoreImpl) GetPets(w http.ResponseWriter, r *http.Request) { - // Implement me -} - -func SetupHandler() { - var myApi PetStoreImpl - - r := gin.Default() - r.Use(middleware.OapiRequestValidator(swagger)) - r = api.RegisterHandlers(r, petStore) -} -``` - -
- -
net/http - -[Chi](https://github.com/go-chi/chi) is 100% compatible with `net/http` allowing the following with code generated using `-generate chi-server`. - -```go -type PetStoreImpl struct {} -func (*PetStoreImpl) GetPets(w http.ResponseWriter, r *http.Request) { - // Implement me -} - -func SetupHandler() { - var myApi PetStoreImpl - - http.Handle("/", Handler(&myApi)) -} -``` - -Alternatively, [Gorilla](https://github.com/gorilla/mux) is also 100% compatible with `net/http` and can be generated with `-generate gorilla`. - -
- -
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. -You can use `-generate std-http` to generate functions to help you associate your handlers with the auto-generated code. -For the pet store, it looks like this: - -```go -// 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 -} -``` - -The wrapper functions referenced above contain generated code which pulls parameters off the request and unmarshals them into Go objects. - -You would register the generated handlers as follows: - -```go -type PetStoreImpl struct {} -func (*PetStoreImpl) GetPets(w http.ResponseWriter, r *http.Request) { - // Implement me -} - -func SetupHandler() { - var myApi PetStoreImpl - - options := petstore.StdHTTPServerOptions{ - BaseRouter: http.DefaultServeMux, // Or use a new ServeMux - } - petstore.HandlerWithOptions(&myApi, options) -} -``` -**Note** that 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 -``` - -
- -
Iris - -Code generated using `-generate iris`. - -The usage of `iris` is out of scope of this doc, but once you have an -iris 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 *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() -} -``` - -```go -import ( - "github.com/kataras/iris/v12" - "github.com/deepmap/oapi-codegen/examples/petstore-expanded/iris/api" - middleware "github.com/oapi-codegen/iris-middleware" -) -``` - -The wrapper functions referenced above contain generated code which pulls -parameters off the `Iris` request context, and unmarshals them into Go objects. - -You would register the generated handlers as follows: - -```go -func SetupHandler() { - var myApi PetStoreImpl - - i := iris.Default() - i.Use(middleware.OapiRequestValidator(swagger)) - api.RegisterHandlers(r, &myApi) -} -``` - -
#### Strict server generation From 54055c8cdedc1fcbe92944cfb5cca577e1e79570 Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Mon, 25 Mar 2024 09:44:00 +0000 Subject: [PATCH 10/20] sq --- README.md | 121 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 62 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 7425f4d011..0d93a01d86 100644 --- a/README.md +++ b/README.md @@ -161,13 +161,9 @@ func SetupHandler() { echo-server -
Echo +
Echo -Code generated using `-generate server`. - -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: +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) { @@ -382,6 +378,65 @@ Alternatively, [Gorilla](https://github.com/gorilla/mux) is also 100% compatible +#### Strict server + +In addition **??**. + +```yaml +# TODO +``` + +This **??** + +``` +--------------------- TODO --------- +``` + +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. + +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 +} +``` + +For a complete example see [`examples/petstore-expanded/strict`](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: + +```go +func SetupHandler() { + var myApi PetStoreImpl + myStrictApiHandler := api.NewStrictHandler(myApi, nil) + e := echo.New() + petstore.RegisterHandlers(e, &myStrictApiHandler) +} +``` + +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. + +``` +--------------------- TODO --------- +``` + ### Generating API clients ### Generating API models @@ -563,61 +618,9 @@ type FindPetsParams struct { } ``` -### 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` - - - - - - -#### Strict server generation - -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. - -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 -} -``` - -For a complete example see [`examples/petstore-expanded/strict`](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: - -```go -func SetupHandler() { - var myApi PetStoreImpl - myStrictApiHandler := api.NewStrictHandler(myApi, nil) - e := echo.New() - petstore.RegisterHandlers(e, &myStrictApiHandler) -} -``` - -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 +[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, the `additionalProperties` field is assumed to be `true`. From ab8b9dddc328196c8574e59db6a4984e829626f7 Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Mon, 25 Mar 2024 09:47:50 +0000 Subject: [PATCH 11/20] sq --- README.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0d93a01d86..05c934b164 100644 --- a/README.md +++ b/README.md @@ -353,9 +353,14 @@ func SetupHandler() { -
net/http +
net/http-compatible servers -[Chi](https://github.com/go-chi/chi) is 100% compatible with `net/http` allowing the following with code generated using `-generate chi-server`. +The following servers implement handlers that are directly compatible with the standard library: + +- gorilla/mux +- Chi + +Therefore, these can be used as-is, for instance: ```go type PetStoreImpl struct {} @@ -370,9 +375,71 @@ func SetupHandler() { } ``` -Alternatively, [Gorilla](https://github.com/gorilla/mux) is also 100% compatible with `net/http` and can be generated with `-generate gorilla`. +
+ +
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. +You can use `-generate std-http` to generate functions to help you associate your handlers with the auto-generated code. +For the pet store, it looks like this: + +```go +// 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 +} +``` + +The wrapper functions referenced above contain generated code which pulls parameters off the request and unmarshals them into Go objects. + +You would register the generated handlers as follows: + +```go +type PetStoreImpl struct {} +func (*PetStoreImpl) GetPets(w http.ResponseWriter, r *http.Request) { + // Implement me +} + +func SetupHandler() { + var myApi PetStoreImpl + + options := petstore.StdHTTPServerOptions{ + BaseRouter: http.DefaultServeMux, // Or use a new ServeMux + } + petstore.HandlerWithOptions(&myApi, options) +} +``` + +**Note** that 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 +```
+ From a209e61bb5f35843ba1a19d7b2d0ec83e511e7ca Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Mon, 25 Mar 2024 10:10:51 +0000 Subject: [PATCH 12/20] docs: add an example for `generate: models` --- examples/only-models/api.yaml | 50 +++++++++++++++++++++++++ examples/only-models/cfg.yaml | 7 ++++ examples/only-models/doc.go | 3 ++ examples/only-models/only-models.gen.go | 14 +++++++ 4 files changed, 74 insertions(+) create mode 100644 examples/only-models/api.yaml create mode 100644 examples/only-models/cfg.yaml create mode 100644 examples/only-models/doc.go create mode 100644 examples/only-models/only-models.gen.go diff --git a/examples/only-models/api.yaml b/examples/only-models/api.yaml new file mode 100644 index 0000000000..4e3d820663 --- /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/deepmap/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..1e1228b9f6 --- /dev/null +++ b/examples/only-models/cfg.yaml @@ -0,0 +1,7 @@ +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/doc.go b/examples/only-models/doc.go new file mode 100644 index 0000000000..a94b7f2271 --- /dev/null +++ b/examples/only-models/doc.go @@ -0,0 +1,3 @@ +package onlymodels + +//go:generate go run github.com/deepmap/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..412e82a10f --- /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/deepmap/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"` +} From 0ee6d2a464a78f5fea91795f15a46a0711a29929 Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Mon, 25 Mar 2024 10:16:18 +0000 Subject: [PATCH 13/20] sq --- README.md | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 113 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 05c934b164..2bd3425433 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ For full https://pkg.go.dev/github.com/deepmap/oapi-codegen/v2/pkg/codegen#Confi - Support of OpenAPI 3.x - OpenAPI 3.1 support is [awaiting upstream support](https://github.com/deepmap/oapi-codegen/issues/373) - Note that this does not include OpenAPI 2.0 (aka Swagger) +- Extract parameters from requests ``` This package tries to be too simple rather than too generic, so we've made some @@ -110,7 +111,7 @@ To provide you a fully Test Driven Development style test harness, you could use Server -generate flag to enable
code generation +generate flag to enable code generation Example usage @@ -131,8 +132,6 @@ Example usage Chi -Code generated using `-generate chi-server`. - ```go type PetStoreImpl struct {} func (*PetStoreImpl) GetPets(w http.ResponseWriter, r *http.Request) { @@ -506,10 +505,121 @@ authorization, etc. Note that middlewares are server-specific. ### Generating API clients +**??** + +This produces code such as: + +```go +// TODO +``` + +You are then able to **??**. + +```go + +``` + ### 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. +For instance, given a `generate.go`: + +```go +package onlymodels + +//go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml +``` + +And a `cfg.yaml`: + +```yaml +package: onlymodels +output: only-models.gen.go +generate: + models: true +``` + +And 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/deepmap/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: int +``` + +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 +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/petstore-expanded/only-models`](examples/petstore-expanded/only-models). + ### Generating Nullable types Opt-in, **??** From 4495985cedc544c7f1dab3efed2f737d19b10581 Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Mon, 25 Mar 2024 10:57:38 +0000 Subject: [PATCH 14/20] docs: add `generate: client` example --- examples/client/api.yaml | 47 +++++ examples/client/cfg.yaml | 5 + examples/client/client.gen.go | 345 ++++++++++++++++++++++++++++++++++ examples/client/doc.go | 3 + 4 files changed, 400 insertions(+) create mode 100644 examples/client/api.yaml create mode 100644 examples/client/cfg.yaml create mode 100644 examples/client/client.gen.go create mode 100644 examples/client/doc.go 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..be3085372a --- /dev/null +++ b/examples/client/cfg.yaml @@ -0,0 +1,5 @@ +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..33ed34d750 --- /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/deepmap/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/doc.go b/examples/client/doc.go new file mode 100644 index 0000000000..3bf610b9ef --- /dev/null +++ b/examples/client/doc.go @@ -0,0 +1,3 @@ +package client + +//go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml From cc80a773d87121c23d4df3df15a20e58b8e6406e Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Mon, 25 Mar 2024 10:57:50 +0000 Subject: [PATCH 15/20] q Signed-off-by: Jamie Tanna --- README.md | 211 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 193 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 2bd3425433..3d5d7844b9 100644 --- a/README.md +++ b/README.md @@ -505,42 +505,200 @@ authorization, etc. Note that middlewares are server-specific. ### Generating API clients -**??** +As well as generating the server-side boilerplate, `oapi-codegen` can also generate API clients. -This produces code such as: +For instance, given an `api.yaml`: -```go -// TODO +```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: int +``` + +And a `cfg.yaml`: + +```yaml +package: client +output: client.gen.go +generate: + models: true + client: true ``` -You are then able to **??**. +And a `generate.go`: ```go +package client +//go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml ``` -### Generating API models +This would then generate: -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. +```go +package client -For instance, given a `generate.go`: +// ... -```go -package onlymodels +// ClientType defines model for ClientType. +type ClientType struct { + Name string `json:"name"` +} -//go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml +// ... + +// 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 +} + +// ... ``` -And a `cfg.yaml`: +With this generated client, it is then possible to construct and utilise the client, for instance: -```yaml -package: onlymodels -output: only-models.gen.go -generate: - models: true +```go +package client_test + +import ( + "context" + "fmt" + "log" + "net/http" + + "github.com/deepmap/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) + } + +} ``` -And an `api.yaml`: +### 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. + +For instance, given an `api.yaml`: ```yaml openapi: "3.0.0" @@ -595,6 +753,23 @@ components: type: int ``` +And a `cfg.yaml`: + +```yaml +package: onlymodels +output: only-models.gen.go +generate: + models: true +``` + +And a `generate.go`: + +```go +package onlymodels + +//go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml +``` + This would then generate: ```go From 57605c2cc5415ff4ebda6b7272eb7d0686621443 Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Mon, 25 Mar 2024 11:32:33 +0000 Subject: [PATCH 16/20] sq --- README.md | 414 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 411 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3d5d7844b9..ed38abaae2 100644 --- a/README.md +++ b/README.md @@ -556,7 +556,7 @@ components: - id properties: id: - type: int + type: integer ``` And a `cfg.yaml`: @@ -750,7 +750,7 @@ components: - id properties: id: - type: int + type: integer ``` And a `cfg.yaml`: @@ -797,7 +797,50 @@ For a complete example see [`examples/petstore-expanded/only-models`](examples/p ### Generating Nullable types -Opt-in, **??** +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/deepmap/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 { + Field nullable.Nullable[string] `json:"field,omitempty"` +} +``` ### OpenAPI extensions @@ -809,6 +852,8 @@ As well as inbuilt OpenAPI, we also support the following OpenAPI extensions. It is possible to extend **??** using the templates **??**. +**Note** that filenames must exactly match **??** + #### Local paths #### HTTPS paths @@ -831,6 +876,369 @@ TODO Alternatively, you are able to use the underlying code generation as a package, which [will be documented in the future](https://github.com/deepmap/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 **??**. + +> [!NOTE] +> In the future [this will be possible](https://github.com/deepmap/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 +``` + +#### Explicit `additionalProperties: true` + +```yaml +components: + schemas: + Thing: + type: object + required: + - id + properties: + id: + type: integer + # explicit true + additionalProperties: true +``` + +Will generate: + +```go +// Thing defines model for Thing. +type Thing struct { + Id int `json:"id"` + AdditionalProperties map[string]interface{} `json:"-"` +} + +// with generated boilerplate below +``` + +
+ +Boilerplate + +```go + +// 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 +} + +// 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 +} + +// 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") + } + + 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 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) + } + + 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) +} +``` + +
+ + +#### `additionalProperties` as `integer`s + +```yaml +components: + schemas: + Thing: + type: object + required: + - id + properties: + id: + type: integer + # simple type + additionalProperties: + type: integer +``` + +Will generate: + +```go +// Thing defines model for Thing. +type Thing struct { + Id int `json:"id"` + AdditionalProperties map[string]int `json:"-"` +} + +// with generated boilerplate below +``` + +
+ +Boilerplate + +```go +// 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 +} + +// 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") + } + + 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 +} + +// 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) + } + + 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) +} +``` + +
+ +#### `additionalProperties` with an object + +```yaml +components: + schemas: + Thing: + type: object + required: + - id + properties: + id: + type: integer + # object + additionalProperties: + type: object + properties: + foo: + type: string +``` + +Will generate: + +```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 +// 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") + } + + 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 +} + +// 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) + } + + 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) +} + +``` + +
+ ## Examples The [examples directory] contains some **??**, including how you'd take the Petstore API and **??**. From f49f077b942d922d225632ec7450bd607186753c Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Mon, 25 Mar 2024 11:39:26 +0000 Subject: [PATCH 17/20] sq --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ed38abaae2..38c212f2bf 100644 --- a/README.md +++ b/README.md @@ -880,13 +880,15 @@ Alternatively, you are able to use the underlying code generation as a package, [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 **??**. +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/deepmap/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. +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`](internal/test/components) schemas and tests. Please look through those tests for more usage examples. + #### Implicit `additionalProperties: true` / no `additionalProperties` set ```yaml @@ -910,7 +912,7 @@ type Thing struct { Id int `json:"id"` } -// with no generated boilerplate +// with no generated boilerplate nor the `AdditionalProperties` field ``` #### Explicit `additionalProperties: true` @@ -1244,13 +1246,13 @@ func (a Thing) MarshalJSON() ([]byte, error) { The [examples directory] contains some **??**, including how you'd take the Petstore API and **??**. - ### Blog posts -The are a number of **??** +We love reading posts by the community about how to use the project. + +Here are a few we've found around the Web: -- TODO -https://www.jvt.me/posts/2022/07/12/go-openapi-server/ +- [Building a Go RESTful API with design-first OpenAPI contracts](https://www.jvt.me/posts/2022/07/12/go-openapi-server/) Got one to add? Please raise a PR! From 9ff7343cd7e19370a44cea2ab33fd5546131bf29 Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Mon, 25 Mar 2024 11:49:27 +0000 Subject: [PATCH 18/20] sq --- README.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 38c212f2bf..7ee81457e7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # `oapi-codegen` -`oapi-codegen` is a command-line tool and library, to convert OpenAPI API specifications to Go code, be it server-side implementations, API clients, or simply HTTP models. +`oapi-codegen` is a command-line tool and library to convert OpenAPI specifications to Go code, be it server-side implementations, API clients, or simply HTTP models. `oapi-codegen` aims to reduce some of the tedious boilerplate that can be found when building or interacting with APIs, and focusses on: @@ -50,18 +50,18 @@ Which then means you can invoke it like so: ## Usage -`oapi-codegen` is largely configured using a YAML configuration file, to simplify the number of flags that users need to remember. +`oapi-codegen` is largely configured using a YAML configuration file, to simplify the number of flags that users need to remember, and to tune various **??**. -For full https://pkg.go.dev/github.com/deepmap/oapi-codegen/v2/pkg/codegen#Configuration +For full details of what is supported, it's worth checking out [the GoDoc for `codegen.Configuration`](https://pkg.go.dev/github.com/deepmap/oapi-codegen/v2/pkg/codegen#Configuration). ## Features `oapi-codegen` supports: -- Generating server-side boilerplate for [a number of servers] TODO -- Generating client API boilerplate -- Generating the types -- Splitting **??** +- Generating server-side boilerplate for [a number of servers] TODO ([docs](#generating-server-side-boilerplate)) +- Generating client API boilerplate ([docs](#generating-api-clients)) +- Generating the types ([docs](#generating-api-models)) +- Splitting **??** ([docs](#import-mapping)) - Also described as ["external refs"] or "Import Mappings" in our documentation ## Key design decisions @@ -458,7 +458,7 @@ This **??** --------------------- TODO --------- ``` -oapi-codegen also supports generating RPC inspired strict server, that will parse request bodies and encode responses. +`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 @@ -795,6 +795,13 @@ output-options: For a complete example see [`examples/petstore-expanded/only-models`](examples/petstore-expanded/only-models). +### Splitting large OpenAPI specs across multiple packages (aka "Import Mapping" or "external references") + + +``` +TODO +``` + ### 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. @@ -811,7 +818,7 @@ S: required: [] ``` -The default behaviour in oapi-codegen is to generate: +The default behaviour in `oapi-codegen` is to generate: ```go type S struct { @@ -887,8 +894,6 @@ For simplicity, and to remove a fair bit of duplication and boilerplate, `oapi-c Below you can see some examples of how `additionalProperties` affects the generated code. -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`](internal/test/components) schemas and tests. Please look through those tests for more usage examples. - #### Implicit `additionalProperties: true` / no `additionalProperties` set ```yaml From a26543149a1b9c0c564daf260ae9deab3a014ca7 Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Wed, 27 Mar 2024 09:29:28 +0000 Subject: [PATCH 19/20] sq --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ee81457e7..540885c7c0 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,16 @@ Which then means you can invoke it like so: //go:generate go run oapi-codegen --config=config.yaml ../../api.yaml ``` +### Pinning to ?> + +While the project does not (yet) have [a defined release cadence](https://github.com/deepmap/oapi-codegen/issues/1519), **??**. + +Therefore, you may want to pin **??**. + +This is officially **recommended**. + +We aim to keep the default branch ready-to-release so you should be able to safely pin. + ## Usage `oapi-codegen` is largely configured using a YAML configuration file, to simplify the number of flags that users need to remember, and to tune various **??**. @@ -1248,8 +1258,9 @@ func (a Thing) MarshalJSON() ([]byte, error) { ## Examples -The [examples directory] contains some **??**, including how you'd take the Petstore API and **??**. +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`. +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 From 09adbb06295d68053330eb9f958743427e0f4326 Mon Sep 17 00:00:00 2001 From: Jamie Tanna Date: Fri, 29 Mar 2024 15:40:25 +0000 Subject: [PATCH 20/20] sq --- examples/authenticated-api-2/api.yaml | 57 ++++ .../authenticated-api-2/stdhttp/config.yaml | 6 + .../authenticated-api-2/stdhttp/generate.go | 3 + examples/authenticated-api-2/stdhttp/impl.go | 14 + .../authenticated-api-2/stdhttp/impl_test.go | 43 +++ .../authenticated-api-2/stdhttp/server.gen.go | 306 ++++++++++++++++++ 6 files changed, 429 insertions(+) create mode 100644 examples/authenticated-api-2/api.yaml create mode 100644 examples/authenticated-api-2/stdhttp/config.yaml create mode 100644 examples/authenticated-api-2/stdhttp/generate.go create mode 100644 examples/authenticated-api-2/stdhttp/impl.go create mode 100644 examples/authenticated-api-2/stdhttp/impl_test.go create mode 100644 examples/authenticated-api-2/stdhttp/server.gen.go diff --git a/examples/authenticated-api-2/api.yaml b/examples/authenticated-api-2/api.yaml new file mode 100644 index 0000000000..18a1e02711 --- /dev/null +++ b/examples/authenticated-api-2/api.yaml @@ -0,0 +1,57 @@ +openapi: 3.0.0 +paths: + /unauthenticated: + get: + operationId: unauthenticated + description: Perform an unauthenticated request + responses: + 200: + description: + content: + application/json: + schema: + $ref: '#/components/schemas/Response' + /apiKey: + get: + operationId: apiKey + description: Perform an authenticated request, using an API Key in the `X-API-Key` header + security: + - apiKey: [] + responses: + 200: + description: + content: + application/json: + schema: + $ref: '#/components/schemas/Response' + /httpBasic: + get: + operationId: httpBasic + description: Perform an authenticated request, using HTTP Basic auth + security: + - basicAuth: [] + responses: + 200: + description: + content: + application/json: + schema: + $ref: '#/components/schemas/Response' + +components: + securitySchemes: + apiKey: + type: apiKey + name: X-API-Key + in: header + basicAuth: + type: http + scheme: basic + schemas: + Response: + type: object + properties: + message: + type: string + required: + - message diff --git a/examples/authenticated-api-2/stdhttp/config.yaml b/examples/authenticated-api-2/stdhttp/config.yaml new file mode 100644 index 0000000000..fc2ed9192c --- /dev/null +++ b/examples/authenticated-api-2/stdhttp/config.yaml @@ -0,0 +1,6 @@ +package: stdhttp +generate: + std-http-server: true + embedded-spec: true + models: true +output: server.gen.go diff --git a/examples/authenticated-api-2/stdhttp/generate.go b/examples/authenticated-api-2/stdhttp/generate.go new file mode 100644 index 0000000000..97332326e2 --- /dev/null +++ b/examples/authenticated-api-2/stdhttp/generate.go @@ -0,0 +1,3 @@ +package stdhttp + +//go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../api.yaml diff --git a/examples/authenticated-api-2/stdhttp/impl.go b/examples/authenticated-api-2/stdhttp/impl.go new file mode 100644 index 0000000000..e43c920d9f --- /dev/null +++ b/examples/authenticated-api-2/stdhttp/impl.go @@ -0,0 +1,14 @@ +package stdhttp + +import "net/http" + +type Server struct{} + +// (GET /apiKey) +func (*Server) ApiKey(w http.ResponseWriter, r *http.Request) {} + +// (GET /httpBasic) +func (*Server) HttpBasic(w http.ResponseWriter, r *http.Request) {} + +// (GET /unauthenticated) +func (*Server) Unauthenticated(w http.ResponseWriter, r *http.Request) {} diff --git a/examples/authenticated-api-2/stdhttp/impl_test.go b/examples/authenticated-api-2/stdhttp/impl_test.go new file mode 100644 index 0000000000..bf5dac141b --- /dev/null +++ b/examples/authenticated-api-2/stdhttp/impl_test.go @@ -0,0 +1,43 @@ +package stdhttp + +import ( + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestServer(t *testing.T) { + var s ServerInterface = &Server{} + + t.Run("unauthenticated", func(t *testing.T) { + t.Run("does not require authentication", func(t *testing.T) { + + r := httptest.NewRequest("GET", "/unauthenticated", nil) + rr := httptest.NewRecorder() + + s.Unauthenticated(rr, r) + + assert.Equal(t, http.StatusOK, rr.Code) + + fmt.Println(io.ReadAll(rr.Body)) + }) + }) + + t.Run("apiKey", func(t *testing.T) { + t.Run("returns **??** when no authentication", func(t *testing.T) { + + r := httptest.NewRequest("GET", "/apiKey", nil) + rr := httptest.NewRecorder() + + s.Unauthenticated(rr, r) + + assert.NotEqual(t, http.StatusOK, rr.Code) + + fmt.Println(io.ReadAll(rr.Body)) + }) + }) +} diff --git a/examples/authenticated-api-2/stdhttp/server.gen.go b/examples/authenticated-api-2/stdhttp/server.gen.go new file mode 100644 index 0000000000..a2ae2de8df --- /dev/null +++ b/examples/authenticated-api-2/stdhttp/server.gen.go @@ -0,0 +1,306 @@ +//go:build go1.22 + +// Package stdhttp provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package stdhttp + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" +) + +const ( + ApiKeyScopes = "apiKey.Scopes" + BasicAuthScopes = "basicAuth.Scopes" +) + +// Response defines model for Response. +type Response struct { + Message string `json:"message"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /apiKey) + ApiKey(w http.ResponseWriter, r *http.Request) + + // (GET /httpBasic) + HttpBasic(w http.ResponseWriter, r *http.Request) + + // (GET /unauthenticated) + Unauthenticated(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 + +// ApiKey operation middleware +func (siw *ServerInterfaceWrapper) ApiKey(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + ctx = context.WithValue(ctx, ApiKeyScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ApiKey(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// HttpBasic operation middleware +func (siw *ServerInterfaceWrapper) HttpBasic(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + ctx = context.WithValue(ctx, BasicAuthScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.HttpBasic(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// Unauthenticated operation middleware +func (siw *ServerInterfaceWrapper) Unauthenticated(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.Unauthenticated(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +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{}) +} + +type StdHTTPServerOptions struct { + BaseURL string + BaseRouter *http.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 *http.ServeMux) http.Handler { + return HandlerWithOptions(si, StdHTTPServerOptions{ + BaseRouter: m, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, m *http.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+"/apiKey", wrapper.ApiKey) + m.HandleFunc("GET "+options.BaseURL+"/httpBasic", wrapper.HttpBasic) + m.HandleFunc("GET "+options.BaseURL+"/unauthenticated", wrapper.Unauthenticated) + + return m +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/8RSzW7bMAx+FYHb0a2D7aZbdmrQS7B1wIAgQFWZsVXElCbRB6PQuw+UG7lrLgMGbLmI", + "CD+S349fwPoxeELiBPoFkh1wNKX8iil4Sih1iD5gZIelM2JKpi8NngOChsTRUQ85NxDx5+QidqAPFXhs", + "LkD/9IyWCzChnaLj+ZucXBab4O5xlsoRaBjQdBihATKjDP+42e53N4Ko+14ncgNPJjm7nXioMqRf/l3h", + "A3OALNcdnTxoms7nBnxAMsGBhs+3m9sNNBAMD4VRu1LqkeXpMNnoAjsvFPcYTz6OypAyEw9I7Kxh7JTY", + "gIkbNSVHvfS3+526x1k5UjygeqxqHlUVKi4bWb3rQMN2uS2eLlEUSp82G3msJ0bixbZwlrPOU/uchNcl", + "SKk+RjyBhg/tmnT7GnNbM845v80E9GFN43DMR2m24t6XYujf2nH38LBXZVXBXSm/q5f+l/g3n1PVP9Fv", + "ov7EhXcjFx+uBH9/t/qfyZbfrwAAAP//ouYjQQUEAAA=", +} + +// 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 +}