Repository: swaggest/rest
Pull request #183 (2023-10-30 13:09:51)

Update deps, add more usage examples

github-actions[bot]: ### Lines Of Code

| Language  |  Files   |    Lines    |    Code     | Comments  |   Blanks   | Complexity |     Bytes      |
|-----------|----------|-------------|-------------|-----------|------------|------------|----------------|
| Go        | 112 (+1) | 8488 (+161) | 6118 (+79)  | 637 (+63) | 1733 (+19) | 1013 (+2)  | 206.4K (+4.2K) |
| Go (test) | 54       | 6371 (+26)  | 4877 (+15)  | 295 (+4)  | 1199 (+7)  | 188 (+2)   | 175.5K (+687B) |
| JSON      | 3        | 1856 (+245) | 1856 (+245) | 0         | 0          | 0          | 71.5K (+7.8K)  |
<!-- Sticky Pull Request CommentLOC -->
github-actions[bot]: ### Go API Changes

<pre>
# summary
Inferred base version: v0.2.58
Suggested version: v0.3.0
</pre>
<!-- Sticky Pull Request Commentgorelease -->
github-actions[bot]: ### Unit Test Coverage
total:								(statements)			81.1%

<details><summary>Coverage of changed lines</summary>

No changes in testable statements.

</details>

<details><summary>Coverage diff with base branch</summary>

No changes in coverage.

</details>
<!-- Sticky Pull Request Commentunit-test -->
codecov[bot]: We're currently processing your upload.  This comment will be updated when the results are available.
github-actions[bot]: ### Benchmark Result
<details><summary>Benchmark diff with base branch</summary>

```
name                                    old time/op    new time/op    delta
pkg:github.com/swaggest/rest/jsonschema goos:linux goarch:amd64
RequestValidator_ValidateRequestData-2    1.41µs ± 4%    1.36µs ± 5%    ~     (p=0.065 n=6+6)
pkg:github.com/swaggest/rest/request goos:linux goarch:amd64
Decoder_Decode-2                           716ns ± 6%     696ns ± 1%    ~     (p=0.132 n=6+6)
DecoderFunc_Decode-2                      2.05µs ± 4%    2.05µs ± 2%    ~     (p=0.818 n=6+6)
Decoder_Decode_json-2                     21.0µs ± 2%    21.2µs ± 2%    ~     (p=0.394 n=6+6)
Decoder_Decode_queryObject-2              4.84µs ± 2%    4.87µs ± 2%    ~     (p=0.394 n=6+6)
Decoder_Decode_jsonParam-2                1.81µs ± 2%    1.84µs ± 2%    ~     (p=0.082 n=6+6)
DecoderFactory_SetDecoderFunc-2           1.75µs ± 2%    1.76µs ± 2%    ~     (p=0.589 n=6+6)
pkg:github.com/swaggest/rest/response/gzip goos:linux goarch:amd64
Middleware-2                              14.3µs ± 4%    13.7µs ± 1%  -4.34%  (p=0.015 n=6+6)
Middleware_control-2                      3.61µs ± 3%    3.62µs ± 3%    ~     (p=0.937 n=6+6)

name                                    old alloc/op   new alloc/op   delta
pkg:github.com/swaggest/rest/jsonschema goos:linux goarch:amd64
RequestValidator_ValidateRequestData-2    2.46kB ± 0%    2.46kB ± 0%    ~     (all equal)
pkg:github.com/swaggest/rest/request goos:linux goarch:amd64
Decoder_Decode-2                            440B ± 0%      440B ± 0%    ~     (all equal)
DecoderFunc_Decode-2                      1.51kB ± 0%    1.51kB ± 0%    ~     (all equal)
Decoder_Decode_json-2                     12.2kB ± 0%    12.2kB ± 0%    ~     (all equal)
Decoder_Decode_queryObject-2              2.00kB ± 0%    2.00kB ± 0%    ~     (all equal)
Decoder_Decode_jsonParam-2                  736B ± 0%      736B ± 0%    ~     (all equal)
DecoderFactory_SetDecoderFunc-2           1.02kB ± 0%    1.02kB ± 0%    ~     (all equal)
pkg:github.com/swaggest/rest/response/gzip goos:linux goarch:amd64
Middleware-2                              1.13kB ± 2%    1.12kB ± 2%    ~     (p=0.069 n=6+6)
Middleware_control-2                      11.2kB ± 0%    11.2kB ± 0%    ~     (all equal)

name                                    old allocs/op  new allocs/op  delta
pkg:github.com/swaggest/rest/jsonschema goos:linux goarch:amd64
RequestValidator_ValidateRequestData-2      8.00 ± 0%      8.00 ± 0%    ~     (all equal)
pkg:github.com/swaggest/rest/request goos:linux goarch:amd64
Decoder_Decode-2                            4.00 ± 0%      4.00 ± 0%    ~     (all equal)
DecoderFunc_Decode-2                        12.0 ± 0%      12.0 ± 0%    ~     (all equal)
Decoder_Decode_json-2                        177 ± 0%       177 ± 0%    ~     (all equal)
Decoder_Decode_queryObject-2                36.0 ± 0%      36.0 ± 0%    ~     (all equal)
Decoder_Decode_jsonParam-2                  13.0 ± 0%      13.0 ± 0%    ~     (all equal)
DecoderFactory_SetDecoderFunc-2             16.0 ± 0%      16.0 ± 0%    ~     (all equal)
pkg:github.com/swaggest/rest/response/gzip goos:linux goarch:amd64
Middleware-2                                11.0 ± 0%      11.0 ± 0%    ~     (all equal)
Middleware_control-2                        9.00 ± 0%      9.00 ± 0%    ~     (all equal)
```
</details>

<details><summary>Benchmark result</summary>

```
name                                    time/op
pkg:github.com/swaggest/rest/jsonschema goos:linux goarch:amd64
RequestValidator_ValidateRequestData-2  1.36µs ± 5%
pkg:github.com/swaggest/rest/request goos:linux goarch:amd64
Decoder_Decode-2                         696ns ± 1%
DecoderFunc_Decode-2                    2.05µs ± 2%
Decoder_Decode_json-2                   21.2µs ± 2%
Decoder_Decode_queryObject-2            4.87µs ± 2%
Decoder_Decode_jsonParam-2              1.84µs ± 2%
DecoderFactory_SetDecoderFunc-2         1.76µs ± 2%
pkg:github.com/swaggest/rest/response/gzip goos:linux goarch:amd64
Middleware-2                            13.7µs ± 1%
Middleware_control-2                    3.62µs ± 3%

name                                    alloc/op
pkg:github.com/swaggest/rest/jsonschema goos:linux goarch:amd64
RequestValidator_ValidateRequestData-2  2.46kB ± 0%
pkg:github.com/swaggest/rest/request goos:linux goarch:amd64
Decoder_Decode-2                          440B ± 0%
DecoderFunc_Decode-2                    1.51kB ± 0%
Decoder_Decode_json-2                   12.2kB ± 0%
Decoder_Decode_queryObject-2            2.00kB ± 0%
Decoder_Decode_jsonParam-2                736B ± 0%
DecoderFactory_SetDecoderFunc-2         1.02kB ± 0%
pkg:github.com/swaggest/rest/response/gzip goos:linux goarch:amd64
Middleware-2                            1.12kB ± 2%
Middleware_control-2                    11.2kB ± 0%

name                                    allocs/op
pkg:github.com/swaggest/rest/jsonschema goos:linux goarch:amd64
RequestValidator_ValidateRequestData-2    8.00 ± 0%
pkg:github.com/swaggest/rest/request goos:linux goarch:amd64
Decoder_Decode-2                          4.00 ± 0%
DecoderFunc_Decode-2                      12.0 ± 0%
Decoder_Decode_json-2                      177 ± 0%
Decoder_Decode_queryObject-2              36.0 ± 0%
Decoder_Decode_jsonParam-2                13.0 ± 0%
DecoderFactory_SetDecoderFunc-2           16.0 ± 0%
pkg:github.com/swaggest/rest/response/gzip goos:linux goarch:amd64
Middleware-2                              11.0 ± 0%
Middleware_control-2                      9.00 ± 0%
```
</details>
<!-- Sticky Pull Request Commentbench -->

Patch:
diff --git a/_examples/advanced-generic-openapi31/router.go b/_examples/advanced-generic-openapi31/router.go
index 0a1e7fc..8b56380 100644
--- a/_examples/advanced-generic-openapi31/router.go
+++ b/_examples/advanced-generic-openapi31/router.go
@@ -26,7 +26,8 @@ import (
 )
 
 func NewRouter() http.Handler {
-	s := web.NewService(openapi31.NewReflector())
+	r := openapi31.NewReflector()
+	s := web.NewService(r)
 
 	s.OpenAPISchema().SetTitle("Advanced Example")
 	s.OpenAPISchema().SetDescription("This app showcases a variety of features.")
@@ -231,6 +232,21 @@ func NewRouter() http.Handler {
 		})
 	})
 
+	// You can also walk the spec and add more information.
+	for p, pi := range r.Spec.Paths.MapOfPathItemValues {
+		pi.Parameters = append(pi.Parameters, openapi31.ParameterOrReference{
+			Parameter: (&openapi31.Parameter{
+				In:   openapi31.ParameterInHeader,
+				Name: "X-Umbrella-Header",
+				Schema: map[string]interface{}{
+					"type": "string",
+				},
+			}).WithDescription("This request header is supported in all operations."),
+		})
+
+		r.Spec.Paths.MapOfPathItemValues[p] = pi
+	}
+
 	// Swagger UI endpoint at /docs.
 	s.Docs("/docs", swgui.New)
 
diff --git a/_examples/advanced/router.go b/_examples/advanced/router.go
index ccf9492..4ef94b8 100644
--- a/_examples/advanced/router.go
+++ b/_examples/advanced/router.go
@@ -22,7 +22,8 @@ func NewRouter() http.Handler {
 	response.DefaultErrorResponseContentType = "application/problem+json"
 	response.DefaultSuccessResponseContentType = "application/dummy+json"
 
-	s := web.NewService(openapi3.NewReflector())
+	r := openapi3.NewReflector()
+	s := web.NewService(r)
 
 	s.OpenAPISchema().SetTitle("Advanced Example")
 	s.OpenAPISchema().SetDescription("This app showcases a variety of features.")
@@ -179,6 +180,21 @@ func NewRouter() http.Handler {
 		})
 	})
 
+	// You can also walk the spec and add more information.
+	for p, pi := range r.Spec.Paths.MapOfPathItemValues {
+		pi.Parameters = append(pi.Parameters, openapi3.ParameterOrRef{
+			Parameter: (&openapi3.Parameter{
+				In:   openapi3.ParameterInHeader,
+				Name: "X-Umbrella-Header",
+				Schema: &openapi3.SchemaOrRef{
+					Schema: (&openapi3.Schema{}).WithType(openapi3.SchemaTypeString),
+				},
+			}).WithDescription("This request header is supported in all operations."),
+		})
+
+		r.Spec.Paths.MapOfPathItemValues[p] = pi
+	}
+
 	// Swagger UI endpoint at /docs.
 	s.Docs("/docs", swgui.New)
 
diff --git a/_examples/go.mod b/_examples/go.mod
index 12aa13a..e5b7929 100644
--- a/_examples/go.mod
+++ b/_examples/go.mod
@@ -11,15 +11,16 @@ require (
 	github.com/bool64/httptestbench v0.1.4
 	github.com/gin-gonic/gin v1.9.1
 	github.com/go-chi/chi/v5 v5.0.10
+	github.com/go-chi/jwtauth/v5 v5.1.1
 	github.com/google/uuid v1.3.0
 	github.com/kelseyhightower/envconfig v1.4.0
 	github.com/rs/cors v1.9.0
 	github.com/stretchr/testify v1.8.4
 	github.com/swaggest/assertjson v1.9.0
-	github.com/swaggest/jsonschema-go v0.3.59
-	github.com/swaggest/openapi-go v0.2.39
+	github.com/swaggest/jsonschema-go v0.3.62
+	github.com/swaggest/openapi-go v0.2.41
 	github.com/swaggest/rest v0.0.0-00010101000000-000000000000
-	github.com/swaggest/swgui v1.7.2
+	github.com/swaggest/swgui v1.7.3
 	github.com/swaggest/usecase v1.2.1
 	github.com/valyala/fasthttp v1.48.0
 )
@@ -31,6 +32,7 @@ require (
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
 	github.com/fsnotify/fsnotify v1.5.4 // indirect
 	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
 	github.com/gin-contrib/sse v0.1.0 // indirect
@@ -43,15 +45,22 @@ require (
 	github.com/klauspost/compress v1.16.7 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.4 // indirect
 	github.com/leodido/go-urn v1.2.4 // indirect
+	github.com/lestrrat-go/blackmagic v1.0.1 // indirect
+	github.com/lestrrat-go/httpcc v1.0.1 // indirect
+	github.com/lestrrat-go/httprc v1.0.4 // indirect
+	github.com/lestrrat-go/iter v1.0.2 // indirect
+	github.com/lestrrat-go/jwx/v2 v2.0.11 // indirect
+	github.com/lestrrat-go/option v1.0.1 // indirect
 	github.com/mattn/go-isatty v0.0.19 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/pelletier/go-toml/v2 v2.0.8 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/santhosh-tekuri/jsonschema/v3 v3.1.0 // indirect
+	github.com/segmentio/asm v1.2.0 // indirect
 	github.com/sergi/go-diff v1.3.1 // indirect
 	github.com/swaggest/form/v5 v5.1.1 // indirect
-	github.com/swaggest/refl v1.2.1 // indirect
+	github.com/swaggest/refl v1.3.0 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 	github.com/ugorji/go/codec v1.2.11 // indirect
 	github.com/valyala/bytebufferpool v1.0.0 // indirect
@@ -61,10 +70,10 @@ require (
 	github.com/yudai/gojsondiff v1.0.0 // indirect
 	github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
 	golang.org/x/arch v0.3.0 // indirect
-	golang.org/x/crypto v0.9.0 // indirect
+	golang.org/x/crypto v0.10.0 // indirect
 	golang.org/x/net v0.10.0 // indirect
-	golang.org/x/sys v0.8.0 // indirect
-	golang.org/x/text v0.9.0 // indirect
+	golang.org/x/sys v0.9.0 // indirect
+	golang.org/x/text v0.13.0 // indirect
 	google.golang.org/protobuf v1.30.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/_examples/go.sum b/_examples/go.sum
index 2bd54c7..6141e2c 100644
--- a/_examples/go.sum
+++ b/_examples/go.sum
@@ -23,6 +23,9 @@ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583j
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
 github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
 github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
@@ -33,6 +36,8 @@ github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
 github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
 github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
 github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
+github.com/go-chi/jwtauth/v5 v5.1.1 h1:Pjixqu5YkjE9sCLpzE01L0Q4sQzJIPdo7uz9r8ftp/c=
+github.com/go-chi/jwtauth/v5 v5.1.1/go.mod h1:CYP1WSbzD4MPuKCr537EM3kfFhSQgpUEtMJFuYJjqWU=
 github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
@@ -66,6 +71,19 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
 github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
+github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
+github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
+github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
+github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
+github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8=
+github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
+github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
+github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
+github.com/lestrrat-go/jwx/v2 v2.0.11 h1:ViHMnaMeaO0qV16RZWBHM7GTrAnX2aFLVKofc7FuKLQ=
+github.com/lestrrat-go/jwx/v2 v2.0.11/go.mod h1:ZtPtMFlrfDrH2Y0iwfa3dRFn8VzwBrB+cyrm3IBWdDg=
+github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
+github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
+github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
 github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
 github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
 github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
@@ -85,6 +103,8 @@ github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE=
 github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
 github.com/santhosh-tekuri/jsonschema/v3 v3.1.0 h1:levPcBfnazlA1CyCMC3asL/QLZkq9pa8tQZOH513zQw=
 github.com/santhosh-tekuri/jsonschema/v3 v3.1.0/go.mod h1:8kzK2TC0k0YjOForaAHdNEa7ik0fokNa2k30BKJ/W7Y=
+github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
+github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
 github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
 github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -92,6 +112,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@@ -104,14 +125,14 @@ github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7
 github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU=
 github.com/swaggest/form/v5 v5.1.1 h1:ct6/rOQBGrqWUQ0FUv3vW5sHvTUb31AwTUWj947N6cY=
 github.com/swaggest/form/v5 v5.1.1/go.mod h1:X1hraaoONee20PMnGNLQpO32f9zbQ0Czfm7iZThuEKg=
-github.com/swaggest/jsonschema-go v0.3.59 h1:BoPhtNvWbZRo1Wl6xvR3g6WL01COJTOAs6vMUnZgocM=
-github.com/swaggest/jsonschema-go v0.3.59/go.mod h1:5WFFGBBte5JAWAV8gDpNRJ/tlQnb1AHDdf/ghgsVUik=
-github.com/swaggest/openapi-go v0.2.39 h1:GfICsAAFnQuyxfywsGyCbPqDKeMXxots4N/9j6+qSCk=
-github.com/swaggest/openapi-go v0.2.39/go.mod h1:g+AfRIkPCHdhqfW8zOD1Sk3PwLhxpWW8SNWHXrmA08c=
-github.com/swaggest/refl v1.2.1 h1:1meX9NaXjM5lmb4kk4RP3OZsXFRke9B1EHAP/pCEKO0=
-github.com/swaggest/refl v1.2.1/go.mod h1:CkC6g7h1PW33KprTuYRSw8UUOslRUt4lF3oe7tTIgNU=
-github.com/swaggest/swgui v1.7.2 h1:N5hMPCQ+bIedVJoQDNjFUn8BqtISQDwaqEa76VkvzLs=
-github.com/swaggest/swgui v1.7.2/go.mod h1:gGFKvKH+nmlPVXBc5S1/sUThCi2f+cthHaY2MfsWlAM=
+github.com/swaggest/jsonschema-go v0.3.62 h1:eIE0aRklWa2eLJg2L/zqyWpKvgUPbq2oKOtrJGJkPH0=
+github.com/swaggest/jsonschema-go v0.3.62/go.mod h1:DYuKqdpms/edvywsX6p1zHXCZkdwB28wRaBdFCe3Duw=
+github.com/swaggest/openapi-go v0.2.41 h1:aO8Q5ZugBmbd16YcppG18e3T+tgU8NJrXA3HLPgnANI=
+github.com/swaggest/openapi-go v0.2.41/go.mod h1:Ww0uMQS11bz3jftWFi6+CA82yl6DHqTv9AE/LLVxzAs=
+github.com/swaggest/refl v1.3.0 h1:PEUWIku+ZznYfsoyheF97ypSduvMApYyGkYF3nabS0I=
+github.com/swaggest/refl v1.3.0/go.mod h1:3Ujvbmh1pfSbDYjC6JGG7nMgPvpG0ehQL4iNonnLNbg=
+github.com/swaggest/swgui v1.7.3 h1:e1Du/3kqvR5HxL41M4pdkJhsADHtuyWpahl8tdcPStM=
+github.com/swaggest/swgui v1.7.3/go.mod h1:FP9uIPTB/QqVs2ZbOD6zc5yTw8SDTJfftak5f4ZRqOQ=
 github.com/swaggest/usecase v1.2.1 h1:XYVdK9tK2KCPglTflUi7aWBrVwIyb58D5mvGWED7pNs=
 github.com/swaggest/usecase v1.2.1/go.mod h1:5ccwVsLJ9eQpU4m0AGTM444pdqSPQBiocIwMmdRH9lQ=
 github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
@@ -133,20 +154,54 @@ github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FB
 github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
 github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
 github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
 golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
-golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
+golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
+golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
+golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
diff --git a/_examples/jwtauth/main.go b/_examples/jwtauth/main.go
new file mode 100644
index 0000000..e60b4d3
--- /dev/null
+++ b/_examples/jwtauth/main.go
@@ -0,0 +1,129 @@
+// Originally from https://github.com/go-chi/jwtauth/blob/v5.1.1/_example/main.go.
+//
+// jwtauth example
+//
+// Sample output:
+//
+// [peter@pak ~]$ curl -v http://localhost:3333/
+// *   Trying ::1...
+// * Connected to localhost (::1) port 3333 (#0)
+// > GET / HTTP/1.1
+// > Host: localhost:3333
+// > User-Agent: curl/7.49.1
+// > Accept: */*
+// >
+// < HTTP/1.1 200 OK
+// < Date: Tue, 13 Sep 2016 15:53:17 GMT
+// < Content-Length: 17
+// < Content-Type: text/plain; charset=utf-8
+// <
+// * Connection #0 to host localhost left intact
+// welcome anonymous%
+//
+//
+// [peter@pak ~]$ curl -v http://localhost:3333/admin
+// *   Trying ::1...
+// * Connected to localhost (::1) port 3333 (#0)
+// > GET /admin HTTP/1.1
+// > Host: localhost:3333
+// > User-Agent: curl/7.49.1
+// > Accept: */*
+// >
+// < HTTP/1.1 401 Unauthorized
+// < Content-Type: text/plain; charset=utf-8
+// < X-Content-Type-Options: nosniff
+// < Date: Tue, 13 Sep 2016 15:53:19 GMT
+// < Content-Length: 13
+// <
+// Unauthorized
+// * Connection #0 to host localhost left intact
+//
+//
+// [peter@pak ~]$ curl -H"Authorization: BEARER eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjN9.PZLMJBT9OIVG2qgp9hQr685oVYFgRgWpcSPmNcw6y7M" -v http://localhost:3333/admin
+// *   Trying ::1...
+// * Connected to localhost (::1) port 3333 (#0)
+// > GET /admin HTTP/1.1
+// > Host: localhost:3333
+// > User-Agent: curl/7.49.1
+// > Accept: */*
+// > Authorization: BEARER eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjN9.PZLMJBT9OIVG2qgp9hQr685oVYFgRgWpcSPmNcw6y7M
+// >
+// < HTTP/1.1 200 OK
+// < Date: Tue, 13 Sep 2016 15:54:26 GMT
+// < Content-Length: 22
+// < Content-Type: text/plain; charset=utf-8
+// <
+// * Connection #0 to host localhost left intact
+// protected area. hi 123%
+//
+
+package main
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+
+	"github.com/go-chi/chi/v5"
+	jwtauth "github.com/go-chi/jwtauth/v5"
+	"github.com/swaggest/openapi-go/openapi31"
+	"github.com/swaggest/rest/nethttp"
+	"github.com/swaggest/rest/web"
+	"github.com/swaggest/usecase"
+)
+
+var tokenAuth *jwtauth.JWTAuth
+
+func init() {
+	tokenAuth = jwtauth.New("HS256", []byte("secret"), nil)
+
+	// For debugging/example purposes, we generate and print
+	// a sample jwt token with claims `user_id:123` here:
+	_, tokenString, _ := tokenAuth.Encode(map[string]interface{}{"user_id": 123})
+	fmt.Printf("DEBUG: a sample jwt is %s\n\n", tokenString)
+}
+
+func main() {
+	addr := "localhost:3333"
+	fmt.Printf("Starting server on http://%v\n", addr)
+	http.ListenAndServe(addr, router())
+}
+
+func Get() usecase.Interactor {
+	u := usecase.NewInteractor(
+		func(ctx context.Context, _ struct{}, output *map[string]interface{}) error {
+			jwt, claims, err := jwtauth.FromContext(ctx)
+			fmt.Println(err)
+			fmt.Println(jwt)
+			fmt.Println(claims)
+
+			*output = claims
+
+			return nil
+		})
+	return u
+}
+
+func router() http.Handler {
+	s := web.NewService(openapi31.NewReflector())
+
+	s.Route("/admin", func(r chi.Router) {
+		r.Group(func(r chi.Router) {
+			r.Use(
+				jwtauth.Verifier(tokenAuth),
+				jwtauth.Authenticator,
+			)
+
+			r.Method(http.MethodGet, "/", nethttp.NewHandler(Get()))
+		})
+	})
+
+	// Public routes
+	s.Group(func(r chi.Router) {
+		r.Get("/", func(w http.ResponseWriter, r *http.Request) {
+			w.Write([]byte("welcome anonymous"))
+		})
+	})
+
+	return s
+}
diff --git a/go.mod b/go.mod
index 65a4fd9..3432e50 100644
--- a/go.mod
+++ b/go.mod
@@ -13,9 +13,9 @@ require (
 	github.com/stretchr/testify v1.8.2
 	github.com/swaggest/assertjson v1.9.0
 	github.com/swaggest/form/v5 v5.1.1
-	github.com/swaggest/jsonschema-go v0.3.59
-	github.com/swaggest/openapi-go v0.2.39
-	github.com/swaggest/refl v1.2.1
+	github.com/swaggest/jsonschema-go v0.3.62
+	github.com/swaggest/openapi-go v0.2.41
+	github.com/swaggest/refl v1.3.0
 	github.com/swaggest/usecase v1.2.1
 )
 
diff --git a/go.sum b/go.sum
index 333a405..1a1248b 100644
--- a/go.sum
+++ b/go.sum
@@ -77,14 +77,12 @@ github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7
 github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU=
 github.com/swaggest/form/v5 v5.1.1 h1:ct6/rOQBGrqWUQ0FUv3vW5sHvTUb31AwTUWj947N6cY=
 github.com/swaggest/form/v5 v5.1.1/go.mod h1:X1hraaoONee20PMnGNLQpO32f9zbQ0Czfm7iZThuEKg=
-github.com/swaggest/jsonschema-go v0.3.57/go.mod h1:5WFFGBBte5JAWAV8gDpNRJ/tlQnb1AHDdf/ghgsVUik=
-github.com/swaggest/jsonschema-go v0.3.59 h1:BoPhtNvWbZRo1Wl6xvR3g6WL01COJTOAs6vMUnZgocM=
-github.com/swaggest/jsonschema-go v0.3.59/go.mod h1:5WFFGBBte5JAWAV8gDpNRJ/tlQnb1AHDdf/ghgsVUik=
-github.com/swaggest/openapi-go v0.2.39 h1:GfICsAAFnQuyxfywsGyCbPqDKeMXxots4N/9j6+qSCk=
-github.com/swaggest/openapi-go v0.2.39/go.mod h1:g+AfRIkPCHdhqfW8zOD1Sk3PwLhxpWW8SNWHXrmA08c=
-github.com/swaggest/refl v1.2.0/go.mod h1:CkC6g7h1PW33KprTuYRSw8UUOslRUt4lF3oe7tTIgNU=
-github.com/swaggest/refl v1.2.1 h1:1meX9NaXjM5lmb4kk4RP3OZsXFRke9B1EHAP/pCEKO0=
-github.com/swaggest/refl v1.2.1/go.mod h1:CkC6g7h1PW33KprTuYRSw8UUOslRUt4lF3oe7tTIgNU=
+github.com/swaggest/jsonschema-go v0.3.62 h1:eIE0aRklWa2eLJg2L/zqyWpKvgUPbq2oKOtrJGJkPH0=
+github.com/swaggest/jsonschema-go v0.3.62/go.mod h1:DYuKqdpms/edvywsX6p1zHXCZkdwB28wRaBdFCe3Duw=
+github.com/swaggest/openapi-go v0.2.41 h1:aO8Q5ZugBmbd16YcppG18e3T+tgU8NJrXA3HLPgnANI=
+github.com/swaggest/openapi-go v0.2.41/go.mod h1:Ww0uMQS11bz3jftWFi6+CA82yl6DHqTv9AE/LLVxzAs=
+github.com/swaggest/refl v1.3.0 h1:PEUWIku+ZznYfsoyheF97ypSduvMApYyGkYF3nabS0I=
+github.com/swaggest/refl v1.3.0/go.mod h1:3Ujvbmh1pfSbDYjC6JGG7nMgPvpG0ehQL4iNonnLNbg=
 github.com/swaggest/usecase v1.2.1 h1:XYVdK9tK2KCPglTflUi7aWBrVwIyb58D5mvGWED7pNs=
 github.com/swaggest/usecase v1.2.1/go.mod h1:5ccwVsLJ9eQpU4m0AGTM444pdqSPQBiocIwMmdRH9lQ=
 github.com/yosuke-furukawa/json5 v0.1.2-0.20201207051438-cf7bb3f354ff h1:7YqG491bE4vstXRz1lD38rbSgbXnirvROz1lZiOnPO8=
@@ -103,6 +101,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -112,12 +111,14 @@ golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwY
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
 golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -143,12 +144,14 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=


Test patch:
diff --git a/_examples/advanced-generic-openapi31/_testdata/openapi.json b/_examples/advanced-generic-openapi31/_testdata/openapi.json
index 53628a7..72ca986 100644
--- a/_examples/advanced-generic-openapi31/_testdata/openapi.json
+++ b/_examples/advanced-generic-openapi31/_testdata/openapi.json
@@ -3,6 +3,12 @@
  "info":{"title":"Advanced Example","description":"This app showcases a variety of features.","version":"v1.2.3"},
  "paths":{
   "/deeper-with-session/one":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "get":{
     "tags":["Other"],"summary":"Dummy","operationId":"_examples/advanced-generic-openapi31.dummy2",
     "responses":{
@@ -16,6 +22,12 @@
    }
   },
   "/deeper-with-session/two":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "get":{
     "tags":["Other"],"summary":"Dummy","operationId":"_examples/advanced-generic-openapi31.dummy3",
     "responses":{
@@ -29,6 +41,12 @@
    }
   },
   "/error-response":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "get":{
     "tags":["Response"],"summary":"Declare Expected Errors",
     "description":"This use case demonstrates documentation of expected errors.",
@@ -69,6 +87,12 @@
    }
   },
   "/file-multi-upload":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "post":{
     "tags":["Request"],"summary":"Files Uploads With 'multipart/form-data'",
     "operationId":"_examples/advanced-generic-openapi31.fileMultiUploader",
@@ -89,6 +113,12 @@
    }
   },
   "/file-upload":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "post":{
     "tags":["Request"],"summary":"File Upload With 'multipart/form-data'",
     "operationId":"_examples/advanced-generic-openapi31.fileUploader",
@@ -106,6 +136,12 @@
    }
   },
   "/form":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "post":{
     "tags":["Request"],"summary":"Request With Form",
     "description":"The `form` field tag acts as `query` and `formData`, with priority on `formData`.\n\nIt is decoded with `http.Request.Form` values.",
@@ -126,6 +162,12 @@
    }
   },
   "/gzip-pass-through":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "get":{
     "tags":["Response"],"summary":"Direct Gzip","operationId":"_examples/advanced-generic-openapi31.directGzip",
     "parameters":[
@@ -163,6 +205,12 @@
    }
   },
   "/html-response/{id}":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "get":{
     "tags":["Response"],"summary":"Request With HTML Response",
     "description":"Request with templated HTML response.",
@@ -182,6 +230,12 @@
    }
   },
   "/json-body-manual/{in-path}":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "post":{
     "tags":["Request"],"summary":"Request With JSON Body and manual decoder",
     "description":"Request with JSON body and query/header/path params, response with JSON body and data from request.",
@@ -211,6 +265,12 @@
    }
   },
   "/json-body-validation/{in-path}":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "post":{
     "tags":["Request","Response","Validation"],"summary":"Request With JSON Body and non-trivial validation",
     "description":"Request with JSON body and query/header/path params, response with JSON body and data from request.",
@@ -240,6 +300,12 @@
    }
   },
   "/json-body/{in-path}":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "post":{
     "tags":["Request"],"summary":"Request With JSON Body",
     "description":"Request with JSON body and query/header/path params, response with JSON body and data from request.",
@@ -269,6 +335,12 @@
    }
   },
   "/json-map-body":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "post":{
     "tags":["Request"],"summary":"Request With JSON Map In Body",
     "description":"Request with JSON object (map) body.",
@@ -294,6 +366,12 @@
    }
   },
   "/json-param/{in-path}":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "get":{
     "tags":["Request"],"summary":"Request With JSON Query Parameter",
     "description":"Request with JSON body and query/header/path params, response with JSON body and data from request.",
@@ -330,6 +408,12 @@
    }
   },
   "/json-slice-body":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "post":{
     "tags":["Request"],"summary":"Request With JSON Array In Body",
     "operationId":"_examples/advanced-generic-openapi31.jsonSliceBody",
@@ -354,6 +438,12 @@
    }
   },
   "/no-validation":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "post":{
     "tags":["Request","Response"],"summary":"No Validation","description":"Input/Output without validation.",
     "operationId":"_examples/advanced-generic-openapi31.noValidation",
@@ -376,6 +466,12 @@
    }
   },
   "/output-csv-writer":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "get":{
     "tags":["Response"],"summary":"Output With Stream Writer","description":"Output with stream writer.",
     "operationId":"_examples/advanced-generic-openapi31.outputCSVWriter",
@@ -408,6 +504,12 @@
    }
   },
   "/output-headers":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "get":{
     "tags":["Response"],"summary":"Output With Headers","description":"Output with headers.",
     "operationId":"_examples/advanced-generic-openapi31.outputHeaders",
@@ -474,6 +576,12 @@
    }
   },
   "/query-object":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "get":{
     "tags":["Request"],"summary":"Request With Object As Query Parameter",
     "operationId":"_examples/advanced-generic-openapi31.queryObject",
@@ -505,6 +613,12 @@
    }
   },
   "/req-resp-mapping":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "post":{
     "tags":["Request","Response"],"summary":"Request Response Mapping",
     "description":"This use case has transport concerns fully decoupled with external req/resp mapping.",
@@ -515,9 +629,7 @@
       "schema":{"description":"Simple scalar value with sample validation.","minLength":3,"type":"string"}
      }
     ],
-    "requestBody":{
-     "content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/FormDataAdvancedInputPort"}}}
-    },
+    "requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/AdvancedInputPort"}}}},
     "responses":{
      "204":{
       "description":"No Content",
@@ -536,6 +648,12 @@
    }
   },
   "/root-with-session":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "get":{
     "tags":["Other"],"summary":"Dummy","operationId":"_examples/advanced-generic-openapi31.dummy",
     "responses":{
@@ -549,6 +667,12 @@
    }
   },
   "/text-req-body-ptr/{path}":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "post":{
     "tags":["Request"],"summary":"Request With Text Body (ptr input)",
     "description":"This usecase allows direct access to original `*http.Request` while keeping automated decoding of parameters.",
@@ -568,6 +692,12 @@
    }
   },
   "/text-req-body/{path}":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "post":{
     "tags":["Request"],"summary":"Request With Text Body",
     "description":"This usecase allows direct access to original `*http.Request` while keeping automated decoding of parameters.",
@@ -587,6 +717,12 @@
    }
   },
   "/validation":{
+   "parameters":[
+    {
+     "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+     "schema":{"type":"string"}
+    }
+   ],
    "post":{
     "tags":["Request","Response","Validation"],"summary":"Validation",
     "description":"Input/Output with validation. Custom annotation.",
@@ -654,6 +790,11 @@
     },
     "type":"object"
    },
+   "AdvancedInputPort":{
+    "additionalProperties":false,
+    "properties":{"val2":{"description":"Simple scalar value with sample validation.","minimum":3,"type":"integer"}},
+    "required":["val2"],"type":"object"
+   },
    "AdvancedInputPortType2":{
     "additionalProperties":false,
     "properties":{
@@ -759,20 +900,12 @@
     "type":"object"
    },
    "FormDataAdvancedForm":{"additionalProperties":false,"properties":{"id":{"type":"integer"},"name":{"type":"string"}},"type":"object"},
-   "FormDataAdvancedInputPort":{
-    "additionalProperties":false,
-    "properties":{"val2":{"description":"Simple scalar value with sample validation.","minimum":3,"type":"integer"}},
-    "required":["val2"],"type":"object"
-   },
    "FormDataAdvancedUpload":{
     "additionalProperties":false,
     "properties":{
      "simple":{"description":"Simple scalar value in body.","type":"string"},
-     "upload1":{
-      "$ref":"#/components/schemas/FormDataMultipartFileHeader",
-      "description":"Upload with *multipart.FileHeader."
-     },
-     "upload2":{"$ref":"#/components/schemas/FormDataMultipartFile","description":"Upload with multipart.File."}
+     "upload1":{"$ref":"#/components/schemas/MultipartFileHeader","description":"Upload with *multipart.FileHeader."},
+     "upload2":{"$ref":"#/components/schemas/MultipartFile","description":"Upload with multipart.File."}
     },
     "type":"object"
    },
@@ -782,17 +915,17 @@
      "simple":{"description":"Simple scalar value in body.","type":"string"},
      "uploads1":{
       "description":"Uploads with *multipart.FileHeader.",
-      "items":{"$ref":"#/components/schemas/FormDataMultipartFileHeader"},"type":["array","null"]
+      "items":{"$ref":"#/components/schemas/MultipartFileHeader"},"type":["array","null"]
      },
      "uploads2":{
-      "description":"Uploads with multipart.File.","items":{"$ref":"#/components/schemas/FormDataMultipartFile"},
+      "description":"Uploads with multipart.File.","items":{"$ref":"#/components/schemas/MultipartFile"},
       "type":["array","null"]
      }
     },
     "type":"object"
    },
-   "FormDataMultipartFile":{"format":"binary","type":["null","string"]},
-   "FormDataMultipartFileHeader":{"format":"binary","type":["null","string"]},
+   "MultipartFile":{"format":"binary","type":["null","string"]},
+   "MultipartFileHeader":{"format":"binary","type":["null","string"]},
    "RestErrResponse":{
     "properties":{
      "code":{"description":"Application-specific error code.","type":"integer"},
diff --git a/_examples/advanced/_testdata/openapi.json b/_examples/advanced/_testdata/openapi.json
index 8f29d9b..8602e49 100644
--- a/_examples/advanced/_testdata/openapi.json
+++ b/_examples/advanced/_testdata/openapi.json
@@ -3,6 +3,12 @@
   "info":{"title":"Advanced Example","description":"This app showcases a variety of features.","version":"v1.2.3"},
   "paths":{
     "/deeper-with-session/one":{
+      "parameters":[
+        {
+          "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+          "schema":{"type":"string"}
+        }
+      ],
       "get":{
         "summary":"Dummy","operationId":"_examples/advanced.dummy2",
         "responses":{
@@ -16,6 +22,12 @@
       }
     },
     "/deeper-with-session/two":{
+      "parameters":[
+        {
+          "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+          "schema":{"type":"string"}
+        }
+      ],
       "get":{
         "summary":"Dummy","operationId":"_examples/advanced.dummy3",
         "responses":{
@@ -29,6 +41,12 @@
       }
     },
     "/dynamic-schema":{
+      "parameters":[
+        {
+          "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+          "schema":{"type":"string"}
+        }
+      ],
       "get":{
         "summary":"Dynamic Request Schema",
         "description":"This use case demonstrates documentation of types that are only known at runtime.",
@@ -61,6 +79,12 @@
       }
     },
     "/error-response":{
+      "parameters":[
+        {
+          "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+          "schema":{"type":"string"}
+        }
+      ],
       "get":{
         "summary":"Declare Expected Errors",
         "description":"This use case demonstrates documentation of expected errors.",
@@ -102,6 +126,12 @@
       }
     },
     "/file-multi-upload":{
+      "parameters":[
+        {
+          "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+          "schema":{"type":"string"}
+        }
+      ],
       "post":{
         "summary":"Files Uploads With 'multipart/form-data'","operationId":"_examples/advanced.fileMultiUploader",
         "parameters":[
@@ -121,6 +151,12 @@
       }
     },
     "/file-upload":{
+      "parameters":[
+        {
+          "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+          "schema":{"type":"string"}
+        }
+      ],
       "post":{
         "summary":"File Upload With 'multipart/form-data'","operationId":"_examples/advanced.fileUploader",
         "parameters":[
@@ -140,6 +176,12 @@
       }
     },
     "/gzip-pass-through":{
+      "parameters":[
+        {
+          "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+          "schema":{"type":"string"}
+        }
+      ],
       "get":{
         "summary":"Direct Gzip","operationId":"_examples/advanced.directGzip",
         "parameters":[
@@ -177,6 +219,12 @@
       }
     },
     "/json-body-validation/{in-path}":{
+      "parameters":[
+        {
+          "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+          "schema":{"type":"string"}
+        }
+      ],
       "post":{
         "summary":"Request With JSON Body and non-trivial validation",
         "description":"Request with JSON body and query/header/path params, response with JSON body and data from request.",
@@ -205,6 +253,12 @@
       }
     },
     "/json-body/{in-path}":{
+      "parameters":[
+        {
+          "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+          "schema":{"type":"string"}
+        }
+      ],
       "post":{
         "summary":"Request With JSON Body",
         "description":"Request with JSON body and query/header/path params, response with JSON body and data from request.",
@@ -234,6 +288,12 @@
       }
     },
     "/json-map-body":{
+      "parameters":[
+        {
+          "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+          "schema":{"type":"string"}
+        }
+      ],
       "post":{
         "summary":"Request With JSON Map In Body","description":"Request with JSON object (map) body.",
         "operationId":"_examples/advanced.jsonMapBody",
@@ -258,6 +318,12 @@
       }
     },
     "/json-param/{in-path}":{
+      "parameters":[
+        {
+          "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+          "schema":{"type":"string"}
+        }
+      ],
       "get":{
         "summary":"Request With JSON Query Parameter",
         "description":"Request with JSON body and query/header/path params, response with JSON body and data from request.",
@@ -294,6 +360,12 @@
       }
     },
     "/json-slice-body":{
+      "parameters":[
+        {
+          "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+          "schema":{"type":"string"}
+        }
+      ],
       "post":{
         "summary":"Request With JSON Array In Body","operationId":"_examples/advanced.jsonSliceBody",
         "parameters":[
@@ -317,6 +389,12 @@
       }
     },
     "/no-validation":{
+      "parameters":[
+        {
+          "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+          "schema":{"type":"string"}
+        }
+      ],
       "post":{
         "summary":"No Validation","description":"Input/Output without validation.",
         "operationId":"_examples/advanced.noValidation",
@@ -339,6 +417,12 @@
       }
     },
     "/output-csv-writer":{
+      "parameters":[
+        {
+          "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+          "schema":{"type":"string"}
+        }
+      ],
       "get":{
         "summary":"Output With Stream Writer","description":"Output with stream writer.",
         "responses":{
@@ -360,6 +444,12 @@
       }
     },
     "/output-headers":{
+      "parameters":[
+        {
+          "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+          "schema":{"type":"string"}
+        }
+      ],
       "get":{
         "summary":"Output With Headers","description":"Output with headers.",
         "responses":{
@@ -391,6 +481,12 @@
       }
     },
     "/query-object":{
+      "parameters":[
+        {
+          "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+          "schema":{"type":"string"}
+        }
+      ],
       "get":{
         "summary":"Request With Object As Query Parameter","operationId":"_examples/advanced.queryObject",
         "parameters":[
@@ -409,6 +505,12 @@
       }
     },
     "/req-resp-mapping":{
+      "parameters":[
+        {
+          "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+          "schema":{"type":"string"}
+        }
+      ],
       "post":{
         "summary":"Request Response Mapping",
         "description":"This use case has transport concerns fully decoupled with external req/resp mapping.",
@@ -419,9 +521,7 @@
             "schema":{"minLength":3,"type":"string","description":"Simple scalar value with sample validation."}
           }
         ],
-        "requestBody":{
-          "content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/FormDataAdvancedInputPort"}}}
-        },
+        "requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/AdvancedInputPort"}}}},
         "responses":{
           "204":{
             "description":"No Content",
@@ -440,6 +540,12 @@
       }
     },
     "/root-with-session":{
+      "parameters":[
+        {
+          "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+          "schema":{"type":"string"}
+        }
+      ],
       "get":{
         "summary":"Dummy","operationId":"_examples/advanced.dummy",
         "responses":{
@@ -453,6 +559,12 @@
       }
     },
     "/validation":{
+      "parameters":[
+        {
+          "name":"X-Umbrella-Header","in":"header","description":"This request header is supported in all operations.",
+          "schema":{"type":"string"}
+        }
+      ],
       "post":{
         "summary":"Validation","description":"Input/Output with validation. Custom annotation.",
         "operationId":"_examples/advanced.validation",
@@ -515,6 +627,11 @@
           "sizes":{"type":"array","items":{"type":"integer"},"nullable":true}
         }
       },
+      "AdvancedInputPort":{
+        "required":["val2"],"type":"object",
+        "properties":{"val2":{"minimum":3,"type":"integer","description":"Simple scalar value with sample validation."}},
+        "additionalProperties":false
+      },
       "AdvancedInputPortType2":{
         "required":["data"],"type":"object",
         "properties":{
@@ -596,17 +713,12 @@
         }
       },
       "DynOut123":{"type":"object","properties":{"bar":{"type":"string"},"status":{"type":"string"}}},
-      "FormDataAdvancedInputPort":{
-        "required":["val2"],"type":"object",
-        "properties":{"val2":{"minimum":3,"type":"integer","description":"Simple scalar value with sample validation."}},
-        "additionalProperties":false
-      },
       "FormDataAdvancedUpload":{
         "type":"object",
         "properties":{
           "simple":{"type":"string","description":"Simple scalar value in body."},
-          "upload1":{"$ref":"#/components/schemas/FormDataMultipartFileHeader"},
-          "upload2":{"$ref":"#/components/schemas/FormDataMultipartFile"}
+          "upload1":{"$ref":"#/components/schemas/MultipartFileHeader"},
+          "upload2":{"$ref":"#/components/schemas/MultipartFile"}
         },
         "additionalProperties":false
       },
@@ -615,18 +727,18 @@
         "properties":{
           "simple":{"type":"string","description":"Simple scalar value in body."},
           "uploads1":{
-            "type":"array","items":{"$ref":"#/components/schemas/FormDataMultipartFileHeader"},
+            "type":"array","items":{"$ref":"#/components/schemas/MultipartFileHeader"},
             "description":"Uploads with *multipart.FileHeader.","nullable":true
           },
           "uploads2":{
-            "type":"array","items":{"$ref":"#/components/schemas/FormDataMultipartFile"},
+            "type":"array","items":{"$ref":"#/components/schemas/MultipartFile"},
             "description":"Uploads with multipart.File.","nullable":true
           }
         },
         "additionalProperties":false
       },
-      "FormDataMultipartFile":{"type":"string","format":"binary","nullable":true},
-      "FormDataMultipartFileHeader":{"type":"string","format":"binary","nullable":true},
+      "MultipartFile":{"type":"string","format":"binary","nullable":true},
+      "MultipartFileHeader":{"type":"string","format":"binary","nullable":true},
       "RestErrResponse":{
         "type":"object",
         "properties":{
diff --git a/openapi/collector_test.go b/openapi/collector_test.go
index 3a3cc86..b312bd0 100644
--- a/openapi/collector_test.go
+++ b/openapi/collector_test.go
@@ -173,9 +173,7 @@ func TestCollector_Collect_requestMapping(t *testing.T) {
 			],
 			"requestBody":{
 			  "content":{
-				"multipart/form-data":{
-				  "schema":{"$ref":"#/components/schemas/FormDataOpenapiTestInput"}
-				}
+				"multipart/form-data":{"schema":{"$ref":"#/components/schemas/OpenapiTestInput"}}
 			  }
 			},
 			"responses":{"204":{"description":"No Content"}},"deprecated":true
@@ -202,9 +200,7 @@ func TestCollector_Collect_requestMapping(t *testing.T) {
 			],
 			"requestBody":{
 			  "content":{
-				"multipart/form-data":{
-				  "schema":{"$ref":"#/components/schemas/FormDataOpenapiTestInput"}
-				}
+				"multipart/form-data":{"schema":{"$ref":"#/components/schemas/OpenapiTestInput"}}
 			  }
 			},
 			"responses":{"204":{"description":"No Content"}},"deprecated":true
@@ -213,12 +209,12 @@ func TestCollector_Collect_requestMapping(t *testing.T) {
 	  },
 	  "components":{
 		"schemas":{
-		  "FormDataMultipartFile":{"type":"string","format":"binary","nullable":true},
-		  "FormDataOpenapiTestInput":{
+		  "MultipartFile":{"type":"string","format":"binary","nullable":true},
+		  "OpenapiTestInput":{
 			"type":"object",
 			"properties":{
 			  "in_form_data":{"type":"string","format":"date-time"},
-			  "upload":{"$ref":"#/components/schemas/FormDataMultipartFile"}
+			  "upload":{"$ref":"#/components/schemas/MultipartFile"}
 			}
 		  }
 		}
diff --git a/request/example_test.go b/request/example_test.go
index 9eda431..eb56955 100644
--- a/request/example_test.go
+++ b/request/example_test.go
@@ -1,6 +1,7 @@
 package request_test
 
 import (
+	"fmt"
 	"net/http"
 
 	"github.com/swaggest/rest/request"
@@ -29,3 +30,32 @@ func ExampleDecoder_Decode() {
 		http.Error(rw, err.Error(), http.StatusBadRequest)
 	}
 }
+
+func ExampleEmbeddedSetter_Request() {
+	type MyRequest struct {
+		request.EmbeddedSetter
+
+		Foo int    `header:"X-Foo"`
+		Bar string `formData:"bar"`
+		Baz bool   `query:"baz"`
+	}
+
+	// A decoder for particular structure, can be reused for multiple HTTP requests.
+	myDecoder := request.NewDecoderFactory().MakeDecoder(http.MethodPost, new(MyRequest), nil)
+
+	// Request and response writer from ServeHTTP.
+	var (
+		rw  http.ResponseWriter
+		req *http.Request
+	)
+
+	// This code would presumably live in ServeHTTP.
+	var myReq MyRequest
+
+	if err := myDecoder.Decode(req, &myReq, nil); err != nil {
+		http.Error(rw, err.Error(), http.StatusBadRequest)
+	}
+
+	// Access data from raw request.
+	fmt.Println("Remote Addr:", myReq.Request().RemoteAddr)
+}