Repository: swaggest/rest
Pull request #135 (2023-02-09 13:03:00)

Add handler option to document text req body, fix req setter and loader

codecov[bot]: # [Codecov](https://codecov.io/gh/swaggest/rest/pull/135?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest) Report
> Merging [#135](https://codecov.io/gh/swaggest/rest/pull/135?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest) (15068d1) into [master](https://codecov.io/gh/swaggest/rest/commit/f931b9709c8a7f8616183520b16bb1c894502eff?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest) (f931b97) will **decrease** coverage by `0.32%`.
> The diff coverage is `46.15%`.

> :exclamation: Current head 15068d1 differs from pull request most recent head 02658a6. Consider uploading reports for the commit 02658a6 to get more accurate results

```diff
@@            Coverage Diff             @@
##           master     #135      +/-   ##
==========================================
- Coverage   77.46%   77.14%   -0.32%     
==========================================
  Files          28       28              
  Lines        1575     1584       +9     
==========================================
+ Hits         1220     1222       +2     
- Misses        233      240       +7     
  Partials      122      122              
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `77.14% <46.15%> (-0.32%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/swaggest/rest/pull/135?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest) | Coverage Δ | |
|---|---|---|
| [nethttp/handler.go](https://codecov.io/gh/swaggest/rest/pull/135?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest#diff-bmV0aHR0cC9oYW5kbGVyLmdv) | `85.22% <ø> (ø)` | |
| [nethttp/options.go](https://codecov.io/gh/swaggest/rest/pull/135?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest#diff-bmV0aHR0cC9vcHRpb25zLmdv) | `73.33% <0.00%> (-13.51%)` | :arrow_down: |
| [request/factory.go](https://codecov.io/gh/swaggest/rest/pull/135?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest#diff-cmVxdWVzdC9mYWN0b3J5Lmdv) | `87.70% <100.00%> (+0.20%)` | :arrow_up: |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest)

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.31µs ± 2%    1.31µs ± 4%    ~     (p=0.554 n=6+6)
pkg:github.com/swaggest/rest/request goos:linux goarch:amd64
Decoder_Decode-2                           665ns ± 0%     669ns ± 0%  +0.61%  (p=0.004 n=6+5)
DecoderFunc_Decode-2                      1.89µs ± 0%    1.91µs ± 1%  +0.95%  (p=0.048 n=5+6)
Decoder_Decode_json-2                     22.1µs ± 5%    21.4µs ± 0%  -3.28%  (p=0.002 n=6+6)
Decoder_Decode_queryObject-2              4.69µs ± 0%    4.69µs ± 0%    ~     (p=0.900 n=6+5)
Decoder_Decode_jsonParam-2                1.86µs ± 1%    1.84µs ± 0%  -1.09%  (p=0.002 n=6+6)
DecoderFactory_SetDecoderFunc-2           1.63µs ± 0%    1.64µs ± 0%  +0.41%  (p=0.026 n=5+6)
pkg:github.com/swaggest/rest/response/gzip goos:linux goarch:amd64
Middleware-2                              10.8µs ± 0%    10.8µs ± 1%    ~     (p=0.937 n=6+6)
Middleware_control-2                      3.35µs ± 2%    3.25µs ± 2%  -3.02%  (p=0.009 n=6+5)

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.16kB ± 3%    1.15kB ± 3%    ~     (p=0.736 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.31µs ± 4%
pkg:github.com/swaggest/rest/request goos:linux goarch:amd64
Decoder_Decode-2                         669ns ± 0%
DecoderFunc_Decode-2                    1.91µs ± 1%
Decoder_Decode_json-2                   21.4µs ± 0%
Decoder_Decode_queryObject-2            4.69µs ± 0%
Decoder_Decode_jsonParam-2              1.84µs ± 0%
DecoderFactory_SetDecoderFunc-2         1.64µs ± 0%
pkg:github.com/swaggest/rest/response/gzip goos:linux goarch:amd64
Middleware-2                            10.8µs ± 1%
Middleware_control-2                    3.25µs ± 2%

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.15kB ± 3%
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 -->
github-actions[bot]: ### Examples Benchmark Result
<details><summary>Benchmark diff with base branch</summary>

```
name                       old time/op    new time/op    delta
pkg:github.com/swaggest/rest/_examples/advanced goos:linux goarch:amd64
_directGzip-2                25.3µs ±13%    26.2µs ±11%     ~     (p=0.151 n=5+5)
_directGzipHead-2            24.1µs ± 1%    25.2µs ± 3%   +4.32%  (p=0.008 n=5+5)
_noDirectGzip-2               129µs ± 1%     129µs ± 1%     ~     (p=0.690 n=5+5)
_directGzip_decode-2          457µs ± 1%     453µs ± 2%     ~     (p=0.151 n=5+5)
_noDirectGzip_decode-2        128µs ± 2%     128µs ± 2%     ~     (p=0.095 n=5+5)
_jsonBody-2                  44.2µs ± 2%    45.6µs ± 4%     ~     (p=0.095 n=5+5)
_jsonBodyValidation-2        49.8µs ± 1%    51.4µs ± 4%   +3.20%  (p=0.032 n=5+5)
_outputHeaders-2             23.5µs ± 4%    24.1µs ± 8%     ~     (p=0.548 n=5+5)
_requestResponseMapping-2    43.1µs ± 2%    43.4µs ± 3%     ~     (p=0.421 n=5+5)
_validation-2                46.8µs ± 4%    47.0µs ± 1%     ~     (p=0.841 n=5+5)
_noValidation-2              34.4µs ± 5%    34.9µs ± 3%     ~     (p=0.421 n=5+5)
pkg:github.com/swaggest/rest/_examples/advanced-generic goos:linux goarch:amd64
_directGzip-2                24.8µs ± 1%    25.4µs ± 2%   +2.37%  (p=0.016 n=5+5)
_directGzipHead-2            25.2µs ± 7%    25.1µs ± 1%     ~     (p=0.190 n=5+4)
_noDirectGzip-2               129µs ± 1%     131µs ± 2%     ~     (p=0.222 n=5+5)
_directGzip_decode-2          459µs ± 2%     470µs ± 2%   +2.43%  (p=0.032 n=5+5)
_noDirectGzip_decode-2        129µs ± 1%     129µs ± 1%     ~     (p=1.000 n=5+5)
_htmlResponse-2              39.3µs ± 1%    39.2µs ± 1%     ~     (p=0.548 n=5+5)
_jsonBodyManual-2            40.4µs ± 3%    28.6µs ± 1%  -29.16%  (p=0.008 n=5+5)
_jsonBody-2                  40.4µs ± 2%    42.0µs ± 2%   +3.99%  (p=0.016 n=5+5)
_jsonBodyValidation-2        52.3µs ± 1%    55.1µs ± 2%   +5.45%  (p=0.008 n=5+5)
_outputHeaders-2             26.6µs ± 3%    25.9µs ± 2%     ~     (p=0.056 n=5+5)
_requestResponseMapping-2    44.8µs ± 2%    44.6µs ± 8%     ~     (p=0.310 n=5+5)
_validation-2                49.6µs ± 5%    47.4µs ± 1%     ~     (p=0.056 n=5+5)
_noValidation-2              36.0µs ± 3%    35.0µs ± 2%   -2.79%  (p=0.032 n=5+5)
pkg:github.com/swaggest/rest/_examples/task-api/internal/infra/nethttp goos:linux goarch:amd64
_notFoundSrv-2               27.4µs ± 2%    28.8µs ±13%     ~     (p=0.222 n=5+5)
_ok-2                        28.9µs ±11%    28.1µs ± 2%     ~     (p=0.690 n=5+5)
_invalidBody-2               40.6µs ± 3%    40.1µs ± 3%     ~     (p=1.000 n=5+5)

name                       old 50%:ms     new 50%:ms     delta
pkg:github.com/swaggest/rest/_examples/advanced goos:linux goarch:amd64
_directGzip-2                  1.21 ± 8%      1.20 ± 5%     ~     (p=0.841 n=5+5)
_directGzipHead-2              1.16 ± 2%      1.18 ± 3%     ~     (p=0.222 n=5+5)
_noDirectGzip-2                6.23 ± 5%      6.19 ± 6%     ~     (p=0.421 n=5+5)
_directGzip_decode-2           18.2 ± 7%      18.5 ± 6%     ~     (p=0.881 n=5+5)
_noDirectGzip_decode-2         6.18 ± 5%      6.36 ± 4%     ~     (p=0.310 n=5+5)
_jsonBody-2                    1.98 ± 4%      2.04 ± 5%     ~     (p=0.151 n=5+5)
_jsonBodyValidation-2          2.22 ± 6%      2.22 ± 4%     ~     (p=1.000 n=5+5)
_outputHeaders-2               1.09 ± 4%      1.09 ± 5%     ~     (p=0.889 n=5+5)
_requestResponseMapping-2      1.87 ± 6%      1.92 ± 5%     ~     (p=0.310 n=5+5)
_validation-2                  1.98 ± 5%      2.01 ± 9%     ~     (p=0.841 n=5+5)
_noValidation-2                1.56 ± 8%      1.56 ± 4%     ~     (p=1.000 n=5+5)
pkg:github.com/swaggest/rest/_examples/advanced-generic goos:linux goarch:amd64
_directGzip-2                  1.19 ± 4%      1.22 ± 5%     ~     (p=0.421 n=5+5)
_directGzipHead-2              1.21 ± 1%      1.23 ± 9%     ~     (p=0.690 n=5+5)
_noDirectGzip-2                6.29 ± 4%      6.42 ± 6%     ~     (p=0.690 n=5+5)
_directGzip_decode-2           18.3 ± 4%      18.5 ± 6%     ~     (p=1.000 n=5+5)
_noDirectGzip_decode-2         6.32 ± 3%      6.34 ± 5%     ~     (p=0.690 n=5+5)
_htmlResponse-2                1.79 ± 8%      1.81 ± 6%     ~     (p=0.421 n=5+5)
_jsonBodyManual-2              1.86 ± 7%      1.33 ± 4%  -28.37%  (p=0.008 n=5+5)
_jsonBody-2                    1.82 ± 6%      1.84 ± 3%     ~     (p=0.690 n=5+5)
_jsonBodyValidation-2          2.23 ± 3%      2.45 ± 6%   +9.48%  (p=0.008 n=5+5)
_outputHeaders-2               1.23 ± 5%      1.17 ±10%     ~     (p=0.095 n=5+5)
_requestResponseMapping-2      1.91 ± 3%      1.97 ± 6%     ~     (p=0.841 n=5+5)
_validation-2                  2.10 ± 5%      2.03 ± 3%     ~     (p=0.310 n=5+5)
_noValidation-2                1.60 ± 7%      1.60 ± 5%     ~     (p=1.000 n=5+5)
pkg:github.com/swaggest/rest/_examples/task-api/internal/infra/nethttp goos:linux goarch:amd64
_notFoundSrv-2                 1.32 ± 7%      1.35 ±16%     ~     (p=1.000 n=5+5)
_ok-2                          1.42 ± 6%      1.34 ± 3%   -6.03%  (p=0.008 n=5+5)
_invalidBody-2                 1.79 ± 5%      1.76 ± 7%     ~     (p=0.548 n=5+5)

name                       old 90%:ms     new 90%:ms     delta
pkg:github.com/swaggest/rest/_examples/advanced goos:linux goarch:amd64
_directGzip-2                  2.05 ± 5%      2.19 ± 6%     ~     (p=0.095 n=5+5)
_directGzipHead-2              2.04 ± 3%      2.14 ± 4%   +5.13%  (p=0.040 n=5+5)
_noDirectGzip-2                11.1 ± 2%      11.3 ± 8%     ~     (p=0.841 n=5+5)
_directGzip_decode-2           38.5 ± 4%      37.0 ±11%     ~     (p=0.222 n=5+5)
_noDirectGzip_decode-2         11.2 ± 5%      11.1 ± 5%     ~     (p=0.841 n=5+5)
_jsonBody-2                    4.00 ± 3%      4.26 ± 7%   +6.35%  (p=0.032 n=5+5)
_jsonBodyValidation-2          4.52 ± 8%      4.60 ± 4%     ~     (p=0.421 n=5+5)
_outputHeaders-2               1.91 ± 5%      2.00 ± 6%     ~     (p=0.151 n=5+5)
_requestResponseMapping-2      3.94 ± 5%      3.94 ± 5%     ~     (p=1.000 n=5+5)
_validation-2                  4.41 ± 6%      4.46 ± 2%     ~     (p=1.000 n=5+5)
_noValidation-2                3.10 ± 5%      3.06 ± 3%     ~     (p=0.310 n=5+5)
pkg:github.com/swaggest/rest/_examples/advanced-generic goos:linux goarch:amd64
_directGzip-2                  2.11 ± 6%      2.19 ± 4%     ~     (p=0.222 n=5+5)
_directGzipHead-2              2.06 ± 5%      2.15 ± 5%     ~     (p=0.095 n=5+5)
_noDirectGzip-2                11.3 ± 3%      11.3 ± 2%     ~     (p=0.548 n=5+5)
_directGzip_decode-2           37.5 ± 4%      40.1 ± 6%   +6.85%  (p=0.032 n=5+5)
_noDirectGzip_decode-2         10.9 ± 3%      11.1 ± 4%     ~     (p=0.548 n=5+5)
_htmlResponse-2                3.56 ± 7%      3.40 ± 2%     ~     (p=0.151 n=5+5)
_jsonBodyManual-2              3.67 ± 7%      2.40 ± 4%  -34.53%  (p=0.008 n=5+5)
_jsonBody-2                    3.68 ± 4%      3.96 ± 4%   +7.75%  (p=0.008 n=5+5)
_jsonBodyValidation-2          4.78 ± 6%      5.10 ± 5%     ~     (p=0.056 n=5+5)
_outputHeaders-2               2.23 ± 7%      2.14 ± 4%     ~     (p=0.222 n=5+5)
_requestResponseMapping-2      4.20 ± 1%      4.24 ± 8%     ~     (p=0.841 n=5+5)
_validation-2                  4.81 ± 7%      4.59 ± 4%     ~     (p=0.151 n=5+5)
_noValidation-2                3.12 ± 4%      3.14 ± 5%     ~     (p=0.690 n=5+5)
pkg:github.com/swaggest/rest/_examples/task-api/internal/infra/nethttp goos:linux goarch:amd64
_notFoundSrv-2                 2.31 ± 5%      2.43 ± 9%     ~     (p=0.310 n=5+5)
_ok-2                          2.37 ± 5%      2.33 ± 5%     ~     (p=0.690 n=5+5)
_invalidBody-2                 3.81 ± 6%      3.66 ± 5%     ~     (p=0.151 n=5+5)

name                       old 99%:ms     new 99%:ms     delta
pkg:github.com/swaggest/rest/_examples/advanced goos:linux goarch:amd64
_directGzip-2                  3.52 ± 9%      3.87 ± 6%     ~     (p=0.114 n=4+4)
_directGzipHead-2              3.51 ± 2%      3.70 ± 3%   +5.46%  (p=0.008 n=5+5)
_noDirectGzip-2                15.6 ± 7%      15.6 ±13%     ~     (p=0.690 n=5+5)
_directGzip_decode-2           64.6 ± 6%      64.4 ±14%     ~     (p=0.690 n=5+5)
_noDirectGzip_decode-2         14.5 ± 7%      15.0 ± 6%     ~     (p=0.095 n=5+5)
_jsonBody-2                    6.56 ± 6%      6.49 ± 8%     ~     (p=0.841 n=5+5)
_jsonBodyValidation-2          7.18 ±12%      8.03 ±31%     ~     (p=0.548 n=5+5)
_outputHeaders-2               4.03 ±32%      3.97 ± 8%     ~     (p=0.730 n=5+4)
_requestResponseMapping-2      7.01 ±15%      6.43 ± 7%     ~     (p=0.421 n=5+5)
_validation-2                  7.96 ±21%      7.21 ±17%     ~     (p=0.548 n=5+5)
_noValidation-2                6.00 ±16%      6.06 ±11%     ~     (p=0.841 n=5+5)
pkg:github.com/swaggest/rest/_examples/advanced-generic goos:linux goarch:amd64
_directGzip-2                  3.64 ± 9%      3.82 ± 6%     ~     (p=0.310 n=5+5)
_directGzipHead-2              3.65 ± 6%      3.83 ± 6%     ~     (p=0.343 n=4+4)
_noDirectGzip-2                15.7 ± 4%      16.0 ± 8%     ~     (p=0.548 n=5+5)
_directGzip_decode-2           62.4 ± 8%      67.8 ±10%     ~     (p=0.063 n=5+5)
_noDirectGzip_decode-2         15.4 ± 3%      14.9 ± 6%     ~     (p=0.151 n=5+5)
_htmlResponse-2                6.42 ±16%      6.18 ± 8%     ~     (p=0.690 n=5+5)
_jsonBodyManual-2              6.29 ±10%      4.73 ± 5%  -24.72%  (p=0.008 n=5+5)
_jsonBody-2                    6.06 ± 9%      6.69 ±13%     ~     (p=0.056 n=5+5)
_jsonBodyValidation-2          7.99 ±14%      8.42 ±10%     ~     (p=0.690 n=5+5)
_outputHeaders-2               4.88 ±16%      4.65 ± 4%     ~     (p=0.421 n=5+5)
_requestResponseMapping-2      7.33 ±14%      7.71 ±31%     ~     (p=0.841 n=5+5)
_validation-2                  7.91 ±10%      7.23 ± 2%     ~     (p=0.111 n=5+4)
_noValidation-2                6.50 ± 7%      5.94 ± 3%   -8.61%  (p=0.016 n=5+5)
pkg:github.com/swaggest/rest/_examples/task-api/internal/infra/nethttp goos:linux goarch:amd64
_notFoundSrv-2                 3.76 ± 5%      5.27 ±93%     ~     (p=0.056 n=5+5)
_ok-2                          5.02 ±88%      3.74 ± 8%     ~     (p=0.056 n=5+5)
_invalidBody-2                 6.94 ±16%      7.03 ±15%     ~     (p=1.000 n=5+5)

name                       old 99.9%:ms   new 99.9%:ms   delta
pkg:github.com/swaggest/rest/_examples/advanced goos:linux goarch:amd64
_directGzip-2                  9.32 ±46%      9.88 ±49%     ~     (p=1.000 n=5+5)
_directGzipHead-2              6.35 ±48%      6.48 ±41%     ~     (p=0.841 n=5+5)
_noDirectGzip-2                20.0 ±11%      20.2 ±15%     ~     (p=1.000 n=5+5)
_directGzip_decode-2           83.9 ± 9%      83.3 ±10%     ~     (p=1.000 n=5+5)
_noDirectGzip_decode-2         17.9 ±13%      19.5 ±16%     ~     (p=0.167 n=5+5)
_jsonBody-2                    10.9 ±25%       9.5 ±21%     ~     (p=0.310 n=5+5)
_jsonBodyValidation-2          12.1 ±26%      15.4 ±56%     ~     (p=0.310 n=5+5)
_outputHeaders-2               9.28 ±30%     10.32 ±48%     ~     (p=0.841 n=5+5)
_requestResponseMapping-2      13.4 ±21%      11.8 ±23%     ~     (p=0.310 n=5+5)
_validation-2                  16.7 ±47%      13.2 ±39%     ~     (p=0.421 n=5+5)
_noValidation-2                11.7 ±41%      12.0 ±33%     ~     (p=1.000 n=5+5)
pkg:github.com/swaggest/rest/_examples/advanced-generic goos:linux goarch:amd64
_directGzip-2                  6.98 ±25%      6.82 ±61%     ~     (p=0.690 n=5+5)
_directGzipHead-2              7.57 ±66%      7.81 ±50%     ~     (p=0.841 n=5+5)
_noDirectGzip-2                20.2 ± 9%      21.0 ±15%     ~     (p=0.690 n=5+5)
_directGzip_decode-2           82.3 ± 7%      84.8 ±10%     ~     (p=0.421 n=5+5)
_noDirectGzip_decode-2         19.0 ± 4%      18.1 ± 1%   -4.74%  (p=0.016 n=5+4)
_htmlResponse-2                11.1 ±15%      11.3 ±23%     ~     (p=1.000 n=5+5)
_jsonBodyManual-2              10.4 ±34%      10.4 ±19%     ~     (p=0.841 n=5+5)
_jsonBody-2                    10.3 ±34%      13.1 ±47%     ~     (p=0.421 n=5+5)
_jsonBodyValidation-2          12.7 ±29%      14.6 ±13%     ~     (p=0.310 n=5+5)
_outputHeaders-2               11.9 ±22%      10.5 ±28%     ~     (p=0.421 n=5+5)
_requestResponseMapping-2      12.0 ±25%      12.6 ±31%     ~     (p=0.841 n=5+5)
_validation-2                  14.3 ±19%      12.4 ±26%     ~     (p=0.095 n=5+5)
_noValidation-2                12.6 ±39%       9.8 ±23%     ~     (p=0.095 n=5+5)
pkg:github.com/swaggest/rest/_examples/task-api/internal/infra/nethttp goos:linux goarch:amd64
_notFoundSrv-2                 7.31 ±25%     10.60 ±81%     ~     (p=0.421 n=5+5)
_ok-2                         10.2 ±109%       5.3 ±15%     ~     (p=0.310 n=5+5)
_invalidBody-2                 11.5 ±34%      10.9 ±31%     ~     (p=0.841 n=5+5)

name                       old B:rcvd/op  new B:rcvd/op  delta
pkg:github.com/swaggest/rest/_examples/advanced goos:linux goarch:amd64
_directGzip-2                   630 ± 0%       630 ± 0%     ~     (all equal)
_directGzipHead-2               174 ± 0%       174 ± 0%     ~     (all equal)
_noDirectGzip-2               1.03k ± 0%     1.03k ± 0%     ~     (all equal)
_directGzip_decode-2            630 ± 0%       630 ± 0%     ~     (all equal)
_noDirectGzip_decode-2        1.03k ± 0%     1.03k ± 0%     ~     (all equal)
_jsonBody-2                     199 ± 0%       199 ± 0%     ~     (all equal)
_jsonBodyValidation-2           185 ± 0%       185 ± 0%     ~     (all equal)
_outputHeaders-2                146 ± 0%       146 ± 0%     ~     (all equal)
_requestResponseMapping-2      94.0 ± 0%      94.0 ± 0%     ~     (all equal)
_validation-2                   168 ± 0%       168 ± 0%     ~     (all equal)
_noValidation-2                 168 ± 0%       168 ± 0%     ~     (all equal)
pkg:github.com/swaggest/rest/_examples/advanced-generic goos:linux goarch:amd64
_directGzip-2                   638 ± 0%       638 ± 0%     ~     (all equal)
_directGzipHead-2               182 ± 0%       182 ± 0%     ~     (all equal)
_noDirectGzip-2               1.04k ± 0%     1.04k ± 0%     ~     (all equal)
_directGzip_decode-2            638 ± 0%       638 ± 0%     ~     (all equal)
_noDirectGzip_decode-2        1.04k ± 0%     1.04k ± 0%     ~     (all equal)
_htmlResponse-2                 355 ± 0%       355 ± 0%     ~     (all equal)
_jsonBodyManual-2               207 ± 0%       207 ± 0%     ~     (all equal)
_jsonBody-2                     207 ± 0%       207 ± 0%     ~     (all equal)
_jsonBodyValidation-2           193 ± 0%       193 ± 0%     ~     (all equal)
_outputHeaders-2                185 ± 0%       185 ± 0%     ~     (all equal)
_requestResponseMapping-2       108 ± 0%       108 ± 0%     ~     (all equal)
_validation-2                   176 ± 0%       176 ± 0%     ~     (all equal)
_noValidation-2                 176 ± 0%       176 ± 0%     ~     (all equal)
pkg:github.com/swaggest/rest/_examples/task-api/internal/infra/nethttp goos:linux goarch:amd64
_notFoundSrv-2                  322 ± 0%       322 ± 0%     ~     (all equal)
_ok-2                           344 ± 0%       344 ± 0%     ~     (all equal)
_invalidBody-2                  420 ± 0%       420 ± 0%     ~     (all equal)

name                       old B:sent/op  new B:sent/op  delta
pkg:github.com/swaggest/rest/_examples/advanced goos:linux goarch:amd64
_directGzip-2                   103 ± 0%       103 ± 0%     ~     (all equal)
_directGzipHead-2               104 ± 0%       104 ± 0%     ~     (all equal)
_noDirectGzip-2                 117 ± 0%       117 ± 0%     ~     (all equal)
_directGzip_decode-2            116 ± 0%       116 ± 0%     ~     (all equal)
_noDirectGzip_decode-2          130 ± 0%       130 ± 0%     ~     (all equal)
_jsonBody-2                     188 ± 0%       188 ± 0%     ~     (all equal)
_jsonBodyValidation-2           192 ± 0%       192 ± 0%     ~     (all equal)
_outputHeaders-2               77.0 ± 0%      77.0 ± 0%     ~     (all equal)
_requestResponseMapping-2       169 ± 0%       169 ± 0%     ~     (all equal)
_validation-2                   170 ± 0%       170 ± 0%     ~     (all equal)
_noValidation-2                 173 ± 0%       173 ± 0%     ~     (all equal)
pkg:github.com/swaggest/rest/_examples/advanced-generic goos:linux goarch:amd64
_directGzip-2                   103 ± 0%       103 ± 0%     ~     (all equal)
_directGzipHead-2               104 ± 0%       104 ± 0%     ~     (all equal)
_noDirectGzip-2                 117 ± 0%       117 ± 0%     ~     (all equal)
_directGzip_decode-2            116 ± 0%       116 ± 0%     ~     (all equal)
_noDirectGzip_decode-2          130 ± 0%       130 ± 0%     ~     (all equal)
_htmlResponse-2                 108 ± 0%       108 ± 0%     ~     (all equal)
_jsonBodyManual-2               195 ± 0%       195 ± 0%     ~     (all equal)
_jsonBody-2                     188 ± 0%       188 ± 0%     ~     (all equal)
_jsonBodyValidation-2           192 ± 0%       192 ± 0%     ~     (all equal)
_outputHeaders-2               77.0 ± 0%      77.0 ± 0%     ~     (all equal)
_requestResponseMapping-2       169 ± 0%       169 ± 0%     ~     (all equal)
_validation-2                   170 ± 0%       170 ± 0%     ~     (all equal)
_noValidation-2                 173 ± 0%       173 ± 0%     ~     (all equal)
pkg:github.com/swaggest/rest/_examples/task-api/internal/infra/nethttp goos:linux goarch:amd64
_notFoundSrv-2                 74.0 ± 0%      74.0 ± 0%     ~     (all equal)
_ok-2                          74.0 ± 0%      74.0 ± 0%     ~     (all equal)
_invalidBody-2                  137 ± 0%       137 ± 0%     ~     (all equal)

name                       old rps        new rps        delta
pkg:github.com/swaggest/rest/_examples/advanced goos:linux goarch:amd64
_directGzip-2                 39.7k ±11%     38.2k ±10%     ~     (p=0.151 n=5+5)
_directGzipHead-2             41.4k ± 1%     39.7k ± 3%   -4.13%  (p=0.008 n=5+5)
_noDirectGzip-2               7.78k ± 1%     7.76k ± 1%     ~     (p=0.690 n=5+5)
_directGzip_decode-2          2.19k ± 1%     2.21k ± 2%     ~     (p=0.151 n=5+5)
_noDirectGzip_decode-2        7.83k ± 2%     7.79k ± 2%     ~     (p=0.095 n=5+5)
_jsonBody-2                   22.6k ± 2%     21.9k ± 4%     ~     (p=0.095 n=5+5)
_jsonBodyValidation-2         20.1k ± 1%     19.5k ± 4%   -3.05%  (p=0.032 n=5+5)
_outputHeaders-2              42.5k ± 4%     41.6k ± 8%     ~     (p=0.548 n=5+5)
_requestResponseMapping-2     23.2k ± 2%     23.0k ± 3%     ~     (p=0.421 n=5+5)
_validation-2                 21.4k ± 4%     21.3k ± 1%     ~     (p=0.841 n=5+5)
_noValidation-2               29.1k ± 4%     28.7k ± 3%     ~     (p=0.421 n=5+5)
pkg:github.com/swaggest/rest/_examples/advanced-generic goos:linux goarch:amd64
_directGzip-2                 40.2k ± 1%     39.3k ± 2%   -2.32%  (p=0.016 n=5+5)
_directGzipHead-2             39.8k ± 7%     39.9k ± 1%     ~     (p=0.190 n=5+4)
_noDirectGzip-2               7.75k ± 1%     7.63k ± 2%     ~     (p=0.167 n=5+5)
_directGzip_decode-2          2.18k ± 2%     2.13k ± 2%   -2.38%  (p=0.032 n=5+5)
_noDirectGzip_decode-2        7.76k ± 1%     7.76k ± 1%     ~     (p=1.000 n=5+5)
_htmlResponse-2               25.5k ± 1%     25.5k ± 1%     ~     (p=0.548 n=5+5)
_jsonBodyManual-2             24.7k ± 3%     34.9k ± 1%  +41.15%  (p=0.008 n=5+5)
_jsonBody-2                   24.7k ± 2%     23.8k ± 2%   -3.84%  (p=0.016 n=5+5)
_jsonBodyValidation-2         19.1k ± 1%     18.1k ± 2%   -5.15%  (p=0.008 n=5+5)
_outputHeaders-2              37.5k ± 2%     38.6k ± 2%     ~     (p=0.056 n=5+5)
_requestResponseMapping-2     22.3k ± 2%     22.4k ± 7%     ~     (p=0.222 n=5+5)
_validation-2                 20.2k ± 5%     21.1k ± 1%     ~     (p=0.056 n=5+5)
_noValidation-2               27.8k ± 3%     28.6k ± 2%   +2.86%  (p=0.032 n=5+5)
pkg:github.com/swaggest/rest/_examples/task-api/internal/infra/nethttp goos:linux goarch:amd64
_notFoundSrv-2                36.4k ± 2%     34.9k ±12%     ~     (p=0.222 n=5+5)
_ok-2                         34.7k ±10%     35.6k ± 2%     ~     (p=0.690 n=5+5)
_invalidBody-2                24.7k ± 3%     24.9k ± 3%     ~     (p=1.000 n=5+5)

name                       old alloc/op   new alloc/op   delta
pkg:github.com/swaggest/rest/_examples/advanced goos:linux goarch:amd64
_directGzip-2                3.99kB ± 0%    3.99kB ± 0%     ~     (p=0.381 n=5+5)
_directGzipHead-2            3.99kB ± 0%    3.99kB ± 0%   +0.06%  (p=0.016 n=5+4)
_noDirectGzip-2              6.82kB ±20%    6.97kB ±19%     ~     (p=0.841 n=5+5)
_directGzip_decode-2          403kB ± 0%     403kB ± 0%     ~     (p=0.841 n=5+5)
_noDirectGzip_decode-2       5.97kB ±11%    5.92kB ± 3%     ~     (p=0.690 n=5+5)
_jsonBody-2                  13.2kB ± 0%    13.2kB ± 0%     ~     (p=1.000 n=5+5)
_jsonBodyValidation-2        19.0kB ± 0%    19.0kB ± 0%     ~     (p=0.508 n=5+5)
_outputHeaders-2             3.71kB ± 0%    3.71kB ± 0%   +0.03%  (p=0.029 n=4+4)
_requestResponseMapping-2    16.7kB ± 0%    16.7kB ± 0%     ~     (p=0.571 n=5+5)
_validation-2                16.6kB ± 0%    16.6kB ± 0%     ~     (p=0.897 n=5+5)
_noValidation-2              7.89kB ± 0%    7.89kB ± 0%     ~     (p=0.635 n=5+5)
pkg:github.com/swaggest/rest/_examples/advanced-generic goos:linux goarch:amd64
_directGzip-2                4.04kB ± 0%    4.04kB ± 0%     ~     (p=0.103 n=5+5)
_directGzipHead-2            4.04kB ± 0%    4.04kB ± 0%     ~     (p=0.579 n=5+5)
_noDirectGzip-2              6.94kB ±18%    7.02kB ±11%     ~     (p=1.000 n=5+5)
_directGzip_decode-2          403kB ± 0%     403kB ± 0%     ~     (p=0.841 n=5+5)
_noDirectGzip_decode-2       5.95kB ± 7%    6.05kB ± 6%     ~     (p=0.690 n=5+5)
_htmlResponse-2              8.28kB ± 0%    8.28kB ± 0%     ~     (p=0.976 n=5+5)
_jsonBodyManual-2            10.4kB ± 0%     4.6kB ± 0%  -55.21%  (p=0.008 n=5+5)
_jsonBody-2                  10.4kB ± 0%    10.4kB ± 0%     ~     (p=0.889 n=5+5)
_jsonBodyValidation-2        19.1kB ± 0%    19.1kB ± 0%     ~     (p=0.833 n=5+5)
_outputHeaders-2             4.36kB ± 0%    4.36kB ± 0%     ~     (p=0.587 n=5+5)
_requestResponseMapping-2    16.8kB ± 0%    16.8kB ± 0%     ~     (p=0.897 n=5+5)
_validation-2                16.7kB ± 0%    16.7kB ± 0%     ~     (p=0.143 n=5+5)
_noValidation-2              7.95kB ± 0%    7.95kB ± 0%     ~     (p=0.524 n=5+5)
pkg:github.com/swaggest/rest/_examples/task-api/internal/infra/nethttp goos:linux goarch:amd64
_notFoundSrv-2               4.88kB ± 0%    4.89kB ± 0%   +0.06%  (p=0.016 n=5+5)
_ok-2                        4.79kB ± 0%    4.79kB ± 0%     ~     (p=1.000 n=4+4)
_invalidBody-2               8.63kB ± 0%    8.63kB ± 0%     ~     (p=0.800 n=4+4)

name                       old allocs/op  new allocs/op  delta
pkg:github.com/swaggest/rest/_examples/advanced goos:linux goarch:amd64
_directGzip-2                  43.0 ± 0%      43.0 ± 0%     ~     (all equal)
_directGzipHead-2              43.0 ± 0%      43.0 ± 0%     ~     (all equal)
_noDirectGzip-2                50.6 ± 3%      50.4 ± 3%     ~     (p=1.000 n=5+5)
_directGzip_decode-2            503 ± 0%       503 ± 0%     ~     (p=0.556 n=5+4)
_noDirectGzip_decode-2         51.0 ± 0%      51.0 ± 0%     ~     (all equal)
_jsonBody-2                     130 ± 0%       130 ± 0%     ~     (all equal)
_jsonBodyValidation-2           186 ± 0%       186 ± 0%     ~     (all equal)
_outputHeaders-2               37.0 ± 0%      37.0 ± 0%     ~     (all equal)
_requestResponseMapping-2       126 ± 0%       126 ± 0%     ~     (all equal)
_validation-2                   155 ± 0%       155 ± 0%     ~     (all equal)
_noValidation-2                92.0 ± 0%      92.0 ± 0%     ~     (all equal)
pkg:github.com/swaggest/rest/_examples/advanced-generic goos:linux goarch:amd64
_directGzip-2                  45.0 ± 0%      45.0 ± 0%     ~     (all equal)
_directGzipHead-2              45.0 ± 0%      45.0 ± 0%     ~     (all equal)
_noDirectGzip-2                52.6 ± 3%      52.6 ± 3%     ~     (p=1.000 n=5+5)
_directGzip_decode-2            505 ± 0%       505 ± 0%     ~     (p=0.968 n=4+5)
_noDirectGzip_decode-2         53.0 ± 0%      53.0 ± 0%     ~     (all equal)
_htmlResponse-2                 146 ± 0%       146 ± 0%     ~     (all equal)
_jsonBodyManual-2               102 ± 0%        51 ± 0%  -50.00%  (p=0.008 n=5+5)
_jsonBody-2                     102 ± 0%       102 ± 0%     ~     (all equal)
_jsonBodyValidation-2           188 ± 0%       188 ± 0%     ~     (all equal)
_outputHeaders-2               46.0 ± 0%      46.0 ± 0%     ~     (all equal)
_requestResponseMapping-2       128 ± 0%       128 ± 0%     ~     (all equal)
_validation-2                   157 ± 0%       157 ± 0%     ~     (all equal)
_noValidation-2                94.0 ± 0%      94.0 ± 0%     ~     (all equal)
pkg:github.com/swaggest/rest/_examples/task-api/internal/infra/nethttp goos:linux goarch:amd64
_notFoundSrv-2                 54.0 ± 0%      54.0 ± 0%     ~     (all equal)
_ok-2                          50.0 ± 0%      50.0 ± 0%     ~     (all equal)
_invalidBody-2                 99.0 ± 0%      99.0 ± 0%     ~     (all equal)
```
</details>
<!-- Sticky Pull Request Commentbench-examples -->

Patch:
diff --git a/.github/workflows/bench-examples.yml b/.github/workflows/bench-examples.yml
index 32242df..6a57b2a 100644
--- a/.github/workflows/bench-examples.yml
+++ b/.github/workflows/bench-examples.yml
@@ -15,7 +15,7 @@ env:
   GO111MODULE: "on"
   CACHE_BENCHMARK: "off"    # Enables benchmark result reuse between runs, may skew latency results.
   RUN_BASE_BENCHMARK: "on"  # Runs benchmark for PR base in case benchmark result is missing.
-  GO_VERSION: 1.18.x
+  GO_VERSION: 1.20.x
 jobs:
   bench:
     runs-on: ubuntu-latest
diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml
index a85f316..dbcbf6d 100644
--- a/.github/workflows/bench.yml
+++ b/.github/workflows/bench.yml
@@ -21,7 +21,7 @@ env:
   GO111MODULE: "on"
   CACHE_BENCHMARK: "off"    # Enables benchmark result reuse between runs, may skew latency results.
   RUN_BASE_BENCHMARK: "on"  # Runs benchmark for PR base in case benchmark result is missing.
-  GO_VERSION: 1.19.x
+  GO_VERSION: 1.20.x
 jobs:
   bench:
     runs-on: ubuntu-latest
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
index fbf8167..e651212 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -21,13 +21,13 @@ jobs:
     steps:
       - uses: actions/setup-go@v3
         with:
-          go-version: 1.19.x
+          go-version: 1.20.x
       - uses: actions/checkout@v2
       - name: golangci-lint
-        uses: golangci/golangci-lint-action@v3.3.1
+        uses: golangci/golangci-lint-action@v3.4.0
         with:
           # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
-          version: v1.50.1
+          version: v1.51.1
 
           # Optional: working directory, useful for monorepos
           # working-directory: somedir
diff --git a/.github/workflows/gorelease.yml b/.github/workflows/gorelease.yml
index 5d9fae0..5fb995e 100644
--- a/.github/workflows/gorelease.yml
+++ b/.github/workflows/gorelease.yml
@@ -9,7 +9,7 @@ concurrency:
   cancel-in-progress: true
 
 env:
-  GO_VERSION: 1.19.x
+  GO_VERSION: 1.20.x
 jobs:
   gorelease:
     runs-on: ubuntu-latest
diff --git a/Makefile b/Makefile
index 62e04df..21caf58 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-#GOLANGCI_LINT_VERSION := "v1.50.1" # Optional configuration to pinpoint golangci-lint version.
+#GOLANGCI_LINT_VERSION := "v1.51.1" # Optional configuration to pinpoint golangci-lint version.
 
 # The head of Makefile determines location of dev-go to include standard targets.
 GO ?= go
diff --git a/_examples/advanced-generic/dummy.go b/_examples/advanced-generic/dummy.go
index 521364b..f9d2db5 100644
--- a/_examples/advanced-generic/dummy.go
+++ b/_examples/advanced-generic/dummy.go
@@ -7,7 +7,10 @@ import (
 )
 
 func dummy() usecase.Interactor {
-	return usecase.NewInteractor(func(ctx context.Context, input struct{}, output *struct{}) error {
+	u := usecase.NewInteractor(func(ctx context.Context, input struct{}, output *struct{}) error {
 		return nil
 	})
+	u.SetTags("Other")
+
+	return u
 }
diff --git a/_examples/advanced-generic/error_response.go b/_examples/advanced-generic/error_response.go
index b98213d..2fa2352 100644
--- a/_examples/advanced-generic/error_response.go
+++ b/_examples/advanced-generic/error_response.go
@@ -42,6 +42,7 @@ func errorResponse() usecase.Interactor {
 	u.SetTitle("Declare Expected Errors")
 	u.SetDescription("This use case demonstrates documentation of expected errors.")
 	u.SetExpectedErrors(status.InvalidArgument, anotherErr{}, status.FailedPrecondition, status.AlreadyExists)
+	u.SetTags("Response")
 
 	return u
 }
diff --git a/_examples/advanced-generic/file_multi_upload.go b/_examples/advanced-generic/file_multi_upload.go
index 665240f..006d5c0 100644
--- a/_examples/advanced-generic/file_multi_upload.go
+++ b/_examples/advanced-generic/file_multi_upload.go
@@ -72,6 +72,7 @@ func fileMultiUploader() usecase.Interactor {
 	})
 
 	u.SetTitle("Files Uploads With 'multipart/form-data'")
+	u.SetTags("Request")
 
 	return u
 }
diff --git a/_examples/advanced-generic/file_upload.go b/_examples/advanced-generic/file_upload.go
index 1cbd309..caf37d1 100644
--- a/_examples/advanced-generic/file_upload.go
+++ b/_examples/advanced-generic/file_upload.go
@@ -72,6 +72,7 @@ func fileUploader() usecase.Interactor {
 	})
 
 	u.SetTitle("File Upload With 'multipart/form-data'")
+	u.SetTags("Request")
 
 	return u
 }
diff --git a/_examples/advanced-generic/form.go b/_examples/advanced-generic/form.go
index edcc63c..ffdfa28 100644
--- a/_examples/advanced-generic/form.go
+++ b/_examples/advanced-generic/form.go
@@ -29,6 +29,7 @@ func form() usecase.Interactor {
 	u.SetTitle("Request With Form")
 	u.SetDescription("The `form` field tag acts as `query` and `formData`, with priority on `formData`.\n\n" +
 		"It is decoded with `http.Request.Form` values.")
+	u.SetTags("Request")
 
 	return u
 }
diff --git a/_examples/advanced-generic/gzip_pass_through.go b/_examples/advanced-generic/gzip_pass_through.go
index b9af581..d2a5c42 100644
--- a/_examples/advanced-generic/gzip_pass_through.go
+++ b/_examples/advanced-generic/gzip_pass_through.go
@@ -85,6 +85,7 @@ func directGzip() usecase.Interactor {
 
 		return nil
 	})
+	u.SetTags("Response")
 
 	return u
 }
diff --git a/_examples/advanced-generic/html_response.go b/_examples/advanced-generic/html_response.go
index a07dc61..bdf6242 100644
--- a/_examples/advanced-generic/html_response.go
+++ b/_examples/advanced-generic/html_response.go
@@ -64,6 +64,7 @@ func htmlResponse() usecase.Interactor {
 
 	u.SetTitle("Request With HTML Response")
 	u.SetDescription("Request with templated HTML response.")
+	u.SetTags("Response")
 
 	return u
 }
diff --git a/_examples/advanced-generic/json_body.go b/_examples/advanced-generic/json_body.go
index a665181..3e923dc 100644
--- a/_examples/advanced-generic/json_body.go
+++ b/_examples/advanced-generic/json_body.go
@@ -41,6 +41,7 @@ func jsonBody() usecase.Interactor {
 
 	u.SetTitle("Request With JSON Body")
 	u.SetDescription("Request with JSON body and query/header/path params, response with JSON body and data from request.")
+	u.SetTags("Request")
 
 	return u
 }
diff --git a/_examples/advanced-generic/json_body_manual.go b/_examples/advanced-generic/json_body_manual.go
index 9de5d37..c16dd9f 100644
--- a/_examples/advanced-generic/json_body_manual.go
+++ b/_examples/advanced-generic/json_body_manual.go
@@ -36,6 +36,7 @@ func jsonBodyManual() usecase.Interactor {
 
 	u.SetTitle("Request With JSON Body and manual decoder")
 	u.SetDescription("Request with JSON body and query/header/path params, response with JSON body and data from request.")
+	u.SetTags("Request")
 
 	return u
 }
diff --git a/_examples/advanced-generic/json_body_validation.go b/_examples/advanced-generic/json_body_validation.go
index f86e3b4..44dcd7c 100644
--- a/_examples/advanced-generic/json_body_validation.go
+++ b/_examples/advanced-generic/json_body_validation.go
@@ -39,6 +39,7 @@ func jsonBodyValidation() usecase.Interactor {
 
 	u.SetTitle("Request With JSON Body and non-trivial validation")
 	u.SetDescription("Request with JSON body and query/header/path params, response with JSON body and data from request.")
+	u.SetTags("Request", "Response", "Validation")
 
 	return u
 }
diff --git a/_examples/advanced-generic/json_map_body.go b/_examples/advanced-generic/json_map_body.go
index 34f37b3..fdac060 100644
--- a/_examples/advanced-generic/json_map_body.go
+++ b/_examples/advanced-generic/json_map_body.go
@@ -37,6 +37,7 @@ func jsonMapBody() usecase.Interactor {
 	})
 
 	u.SetTitle("Request With JSON Map In Body")
+	u.SetTags("Request")
 
 	return u
 }
diff --git a/_examples/advanced-generic/json_param.go b/_examples/advanced-generic/json_param.go
index 09a2340..9a58205 100644
--- a/_examples/advanced-generic/json_param.go
+++ b/_examples/advanced-generic/json_param.go
@@ -41,6 +41,7 @@ func jsonParam() usecase.Interactor {
 
 	u.SetTitle("Request With JSON Query Parameter")
 	u.SetDescription("Request with JSON body and query/header/path params, response with JSON body and data from request.")
+	u.SetTags("Request")
 
 	return u
 }
diff --git a/_examples/advanced-generic/json_slice_body.go b/_examples/advanced-generic/json_slice_body.go
index 579f2d7..aa09884 100644
--- a/_examples/advanced-generic/json_slice_body.go
+++ b/_examples/advanced-generic/json_slice_body.go
@@ -37,6 +37,7 @@ func jsonSliceBody() usecase.Interactor {
 	})
 
 	u.SetTitle("Request With JSON Array In Body")
+	u.SetTags("Request")
 
 	return u
 }
diff --git a/_examples/advanced-generic/no_validation.go b/_examples/advanced-generic/no_validation.go
index 846390d..8cc1b25 100644
--- a/_examples/advanced-generic/no_validation.go
+++ b/_examples/advanced-generic/no_validation.go
@@ -35,6 +35,7 @@ func noValidation() usecase.Interactor {
 
 	u.SetTitle("No Validation")
 	u.SetDescription("Input/Output without validation.")
+	u.SetTags("Request", "Response")
 
 	return u
 }
diff --git a/_examples/advanced-generic/output_headers.go b/_examples/advanced-generic/output_headers.go
index dfba834..7a30902 100644
--- a/_examples/advanced-generic/output_headers.go
+++ b/_examples/advanced-generic/output_headers.go
@@ -25,6 +25,7 @@ func outputHeaders() usecase.Interactor {
 
 	u.SetTitle("Output With Headers")
 	u.SetDescription("Output with headers.")
+	u.SetTags("Response")
 
 	return u
 }
diff --git a/_examples/advanced-generic/output_writer.go b/_examples/advanced-generic/output_writer.go
index 4fbc981..2ac4aeb 100644
--- a/_examples/advanced-generic/output_writer.go
+++ b/_examples/advanced-generic/output_writer.go
@@ -26,6 +26,7 @@ func outputCSVWriter() usecase.Interactor {
 	u.SetTitle("Output With Stream Writer")
 	u.SetDescription("Output with stream writer.")
 	u.SetExpectedErrors(status.Internal)
+	u.SetTags("Response")
 
 	return u
 }
diff --git a/_examples/advanced-generic/query_object.go b/_examples/advanced-generic/query_object.go
index 5ea264a..88bf7f8 100644
--- a/_examples/advanced-generic/query_object.go
+++ b/_examples/advanced-generic/query_object.go
@@ -24,6 +24,7 @@ func queryObject() usecase.Interactor {
 	})
 
 	u.SetTitle("Request With Object As Query Parameter")
+	u.SetTags("Request")
 
 	return u
 }
diff --git a/_examples/advanced-generic/request_response_mapping.go b/_examples/advanced-generic/request_response_mapping.go
index 4cab62a..39a5d7d 100644
--- a/_examples/advanced-generic/request_response_mapping.go
+++ b/_examples/advanced-generic/request_response_mapping.go
@@ -29,6 +29,7 @@ func reqRespMapping() usecase.Interactor {
 	u.SetTitle("Request Response Mapping")
 	u.SetName("reqRespMapping")
 	u.SetDescription("This use case has transport concerns fully decoupled with external req/resp mapping.")
+	u.SetTags("Request", "Response")
 
 	return u
 }
diff --git a/_examples/advanced-generic/request_text_body.go b/_examples/advanced-generic/request_text_body.go
new file mode 100644
index 0000000..38b8240
--- /dev/null
+++ b/_examples/advanced-generic/request_text_body.go
@@ -0,0 +1,69 @@
+package main
+
+import (
+	"context"
+	"io"
+	"net/http"
+
+	"github.com/swaggest/usecase"
+)
+
+type textReqBodyInput struct {
+	Path  string `path:"path"`
+	Query int    `query:"query"`
+	text  []byte
+	err   error
+}
+
+func (c *textReqBodyInput) SetRequest(r *http.Request) {
+	c.text, c.err = io.ReadAll(r.Body)
+	clErr := r.Body.Close()
+
+	if c.err == nil {
+		c.err = clErr
+	}
+}
+
+func textReqBody() usecase.Interactor {
+	type output struct {
+		Path  string `json:"path"`
+		Query int    `json:"query"`
+		Text  string `json:"text"`
+	}
+
+	u := usecase.NewInteractor(func(ctx context.Context, in textReqBodyInput, out *output) (err error) {
+		out.Text = string(in.text)
+		out.Path = in.Path
+		out.Query = in.Query
+
+		return nil
+	})
+
+	u.SetTitle("Request With Text Body")
+	u.SetDescription("This usecase allows direct access to original `*http.Request` while keeping automated decoding of parameters.")
+	u.SetTags("Request")
+
+	return u
+}
+
+func textReqBodyPtr() usecase.Interactor {
+	type output struct {
+		Path  string `json:"path"`
+		Query int    `json:"query"`
+		Text  string `json:"text"`
+	}
+
+	u := usecase.NewInteractor(func(ctx context.Context, in *textReqBodyInput, out *output) (err error) {
+		out.Text = string(in.text)
+		out.Path = in.Path
+		out.Query = in.Query
+
+		return nil
+	})
+
+	u.SetTitle("Request With Text Body (ptr input)")
+	u.SetDescription("This usecase allows direct access to original `*http.Request` while keeping automated decoding of parameters.")
+	u.SetTags("Request")
+
+	return u
+}
diff --git a/_examples/advanced-generic/router.go b/_examples/advanced-generic/router.go
index f8f1b9a..041c3f6 100644
--- a/_examples/advanced-generic/router.go
+++ b/_examples/advanced-generic/router.go
@@ -190,6 +190,8 @@ func NewRouter() http.Handler {
 	s.Head("/gzip-pass-through", directGzip())
 
 	s.Get("/error-response", errorResponse())
+	s.Post("/text-req-body/{path}", textReqBody(), nethttp.RequestBodyContent("text/csv"))
+	s.Post("/text-req-body-ptr/{path}", textReqBodyPtr(), nethttp.RequestBodyContent("text/csv"))
 
 	// Security middlewares.
 	//  - sessMW is the actual request-level processor,
diff --git a/_examples/advanced-generic/validation.go b/_examples/advanced-generic/validation.go
index 065571b..f4e66c5 100644
--- a/_examples/advanced-generic/validation.go
+++ b/_examples/advanced-generic/validation.go
@@ -35,6 +35,7 @@ func validation() usecase.Interactor {
 
 	u.SetTitle("Validation")
 	u.SetDescription("Input/Output with validation.")
+	u.SetTags("Request", "Response", "Validation")
 
 	return u
 }
diff --git a/_examples/go.mod b/_examples/go.mod
index ddb8f65..5f5746a 100644
--- a/_examples/go.mod
+++ b/_examples/go.mod
@@ -5,18 +5,18 @@ go 1.18
 replace github.com/swaggest/rest => ../
 
 require (
-	github.com/bool64/ctxd v1.2.0
-	github.com/bool64/dev v0.2.24
+	github.com/bool64/ctxd v1.2.1
+	github.com/bool64/dev v0.2.25
 	github.com/bool64/httpmock v0.1.8
 	github.com/bool64/httptestbench v0.1.4
 	github.com/go-chi/chi/v5 v5.0.8
 	github.com/kelseyhightower/envconfig v1.4.0
-	github.com/rs/cors v1.8.2
+	github.com/rs/cors v1.8.3
 	github.com/stretchr/testify v1.8.1
 	github.com/swaggest/assertjson v1.7.0
 	github.com/swaggest/jsonschema-go v0.3.47
 	github.com/swaggest/openapi-go v0.2.28
-	github.com/swaggest/swgui v1.5.1
+	github.com/swaggest/swgui v1.6.0
 	github.com/swaggest/usecase v1.2.1
 	github.com/valyala/fasthttp v1.44.0
 )
diff --git a/_examples/go.sum b/_examples/go.sum
index dff0af1..1113dc8 100644
--- a/_examples/go.sum
+++ b/_examples/go.sum
@@ -1,13 +1,13 @@
 github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
 github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
 github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
-github.com/bool64/ctxd v1.2.0 h1:x/ObTZswHT7TNJdQcPWw6YplnZ3oRyFZ+i+YqDb9Ib4=
-github.com/bool64/ctxd v1.2.0/go.mod h1:cSMhQNUcfu8XZRAUSrrFZhNd4DzK6LFN3DR5yeGW74o=
+github.com/bool64/ctxd v1.2.1 h1:hARFteq0zdn4bwfmxLhak3fXFuvtJVKDH2X29VV/2ls=
+github.com/bool64/ctxd v1.2.1/go.mod h1:ZG6QkeGVLTiUl2mxPpyHmFhDzFZCyocr9hluBV3LYuc=
 github.com/bool64/dev v0.1.41/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
 github.com/bool64/dev v0.2.5/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
 github.com/bool64/dev v0.2.22/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
-github.com/bool64/dev v0.2.24 h1:xptlKivPh870W3Xc9szPcM7wkFmTMuHT8rc0nu7dITk=
-github.com/bool64/dev v0.2.24/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
+github.com/bool64/dev v0.2.25 h1:p6euAfe1zLXb1qzLssm0lJnM5KhfUZp/Qjb2dsPkIKU=
+github.com/bool64/dev v0.2.25/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
 github.com/bool64/httpmock v0.1.8 h1:plNJylRdMGjNDV8kCuapKTJJS8p6zbvWbR8NG+Z6qAA=
 github.com/bool64/httpmock v0.1.8/go.mod h1:3T4cLULWI5qlTx+tSF6YaiAWq4IZ1aP//klpumgPj1s=
 github.com/bool64/httptestbench v0.1.4 h1:35f9RwWqcnQRXM+sA+GUhWVGSa6XEFlKpNSH9oYzOjI=
@@ -44,8 +44,8 @@ github.com/onsi/ginkgo v1.15.2 h1:l77YT15o814C2qVL47NOyjV/6RbaP7kKdrvZnxQ3Org=
 github.com/onsi/gomega v1.11.0 h1:+CqWgvj0OZycCaqclBD1pxKHAU+tOkHmQIWvDHq2aug=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U=
-github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
+github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo=
+github.com/rs/cors v1.8.3/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/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
@@ -68,8 +68,8 @@ github.com/swaggest/openapi-go v0.2.28 h1:PRgJTqRfpsWte7qPqMFJooLu8cWyL91FhpYH2k
 github.com/swaggest/openapi-go v0.2.28/go.mod h1:2RAaFLmRFQiR8TQJyIZzh2NTY1ihk3/WxYJFxQ+TfFU=
 github.com/swaggest/refl v1.1.0 h1:a+9a75Kv6ciMozPjVbOfcVTEQe81t2R3emvaD9oGQGc=
 github.com/swaggest/refl v1.1.0/go.mod h1:g3Qa6ki0A/L2yxiuUpT+cuBURuRaltF5SDQpg1kMZSY=
-github.com/swaggest/swgui v1.5.1 h1:CXJ2l9n1nEz2AJXl/FHDGD5ZtlKnTRJb2p86njBfXfo=
-github.com/swaggest/swgui v1.5.1/go.mod h1:lKfAIKTFBStjUtyXz56OQpXgQtpGZAWWIBv4QJtxRQA=
+github.com/swaggest/swgui v1.6.0 h1:JkaYcgqZrI0iZKtWmDOUDbVBGk63UKvHuiCN0tqgxjw=
+github.com/swaggest/swgui v1.6.0/go.mod h1:WNuR8igTMETVe8eEorLncXtLH5zpezD7KHiG7RnQR98=
 github.com/swaggest/usecase v1.2.1 h1:XYVdK9tK2KCPglTflUi7aWBrVwIyb58D5mvGWED7pNs=
 github.com/swaggest/usecase v1.2.1/go.mod h1:5ccwVsLJ9eQpU4m0AGTM444pdqSPQBiocIwMmdRH9lQ=
 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
diff --git a/go.mod b/go.mod
index ecc6685..8b034c5 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/swaggest/rest
 go 1.17
 
 require (
-	github.com/bool64/dev v0.2.24
+	github.com/bool64/dev v0.2.25
 	github.com/bool64/httpmock v0.1.8
 	github.com/bool64/shared v0.1.5
 	github.com/cespare/xxhash/v2 v2.2.0
diff --git a/go.sum b/go.sum
index 4816bd6..f0ef98d 100644
--- a/go.sum
+++ b/go.sum
@@ -3,8 +3,9 @@ github.com/bool64/dev v0.2.10/go.mod h1:/csLrm+4oDSsKJRIVS0mrywAonLnYKFG8RvGT7Jh
 github.com/bool64/dev v0.2.16/go.mod h1:/csLrm+4oDSsKJRIVS0mrywAonLnYKFG8RvGT7Jh9b8=
 github.com/bool64/dev v0.2.17/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
 github.com/bool64/dev v0.2.22/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
-github.com/bool64/dev v0.2.24 h1:xptlKivPh870W3Xc9szPcM7wkFmTMuHT8rc0nu7dITk=
 github.com/bool64/dev v0.2.24/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
+github.com/bool64/dev v0.2.25 h1:p6euAfe1zLXb1qzLssm0lJnM5KhfUZp/Qjb2dsPkIKU=
+github.com/bool64/dev v0.2.25/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
 github.com/bool64/httpmock v0.1.8 h1:plNJylRdMGjNDV8kCuapKTJJS8p6zbvWbR8NG+Z6qAA=
 github.com/bool64/httpmock v0.1.8/go.mod h1:3T4cLULWI5qlTx+tSF6YaiAWq4IZ1aP//klpumgPj1s=
 github.com/bool64/shared v0.1.4/go.mod h1:ryGjsnQFh6BnEXClfVlEJrzjwzat7CmA8PNS5E+jPp0=
diff --git a/nethttp/handler.go b/nethttp/handler.go
index 41af5ef..a28f129 100644
--- a/nethttp/handler.go
+++ b/nethttp/handler.go
@@ -243,6 +243,7 @@ func HandlerWithRouteMiddleware(method, pathPattern string) func(http.Handler) h
 
 // RequestDecoder maps data from http.Request into structured Go input value.
 type RequestDecoder interface {
+	// Decode fills input with data from request, input should be a pointer.
 	Decode(r *http.Request, input interface{}, validator rest.Validator) error
 }
 
diff --git a/nethttp/options.go b/nethttp/options.go
index f2fcf2c..f5892d4 100644
--- a/nethttp/options.go
+++ b/nethttp/options.go
@@ -33,6 +33,20 @@ func AnnotateOperation(annotations ...func(operation *openapi3.Operation) error)
 	}
 }
 
+// RequestBodyContent enables string request body with content type (e.g. text/plain).
+func RequestBodyContent(contentType string) func(h *Handler) {
+	return func(h *Handler) {
+		mt := openapi3.MediaType{}
+		mt.SchemaEns().SchemaEns().WithType(openapi3.SchemaTypeString)
+
+		h.OperationAnnotations = append(h.OperationAnnotations, func(op *openapi3.Operation) error {
+			op.RequestBodyEns().RequestBodyEns().WithContentItem(contentType, mt)
+
+			return nil
+		})
+	}
+}
+
 // SuccessfulResponseContentType sets Content-Type of successful response.
 func SuccessfulResponseContentType(contentType string) func(h *Handler) {
 	return func(h *Handler) {
diff --git a/request/factory.go b/request/factory.go
index c7e4356..a250af3 100644
--- a/request/factory.go
+++ b/request/factory.go
@@ -156,13 +156,13 @@ func initDecoder(input interface{}) decoder {
 		in:       make([]rest.ParamIn, 0),
 	}
 
-	if _, ok := input.(Loader); ok {
-		d.isReqLoader = true
-	}
+	loader := reflect.TypeOf((*Loader)(nil)).Elem()
+	d.isReqLoader = reflect.TypeOf(input).Implements(loader) ||
+		reflect.New(reflect.TypeOf(input)).Type().Implements(loader)
 
-	if _, ok := input.(Setter); ok {
-		d.isReqSetter = true
-	}
+	setter := reflect.TypeOf((*Setter)(nil)).Elem()
+	d.isReqSetter = reflect.TypeOf(input).Implements(setter) ||
+		reflect.New(reflect.TypeOf(input)).Type().Implements(setter)
 
 	return d
 }


Test patch:
diff --git a/.github/workflows/test-examples.yml b/.github/workflows/test-examples.yml
index d44ef73..a6d966d 100644
--- a/.github/workflows/test-examples.yml
+++ b/.github/workflows/test-examples.yml
@@ -11,7 +11,7 @@ jobs:
   test:
     strategy:
       matrix:
-        go-version: [1.19.x ]
+        go-version: [1.20.x ]
     runs-on: ubuntu-latest
     steps:
       - name: Install Go stable
diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml
index c3110ce..4835bf0 100644
--- a/.github/workflows/test-unit.yml
+++ b/.github/workflows/test-unit.yml
@@ -15,13 +15,13 @@ concurrency:
 env:
   GO111MODULE: "on"
   RUN_BASE_COVERAGE: "on"  # Runs test for PR base in case base test coverage is missing.
-  COV_GO_VERSION: 1.18.x   # Version of Go to collect coverage
+  COV_GO_VERSION: 1.20.x   # Version of Go to collect coverage
   TARGET_DELTA_COV: 90     # Target coverage of changed lines, in percents
 jobs:
   test:
     strategy:
       matrix:
-        go-version: [ 1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x, 1.19.x ]
+        go-version: [ 1.13.x, 1.19.x,  1.20.x ]
     runs-on: ubuntu-latest
     steps:
       - name: Install Go stable
diff --git a/_examples/advanced-generic/_testdata/openapi.json b/_examples/advanced-generic/_testdata/openapi.json
index 90162a4..c08cbd9 100644
--- a/_examples/advanced-generic/_testdata/openapi.json
+++ b/_examples/advanced-generic/_testdata/openapi.json
@@ -4,7 +4,7 @@
   "paths":{
     "/deeper-with-session/one":{
       "get":{
-        "summary":"Dummy","operationId":"_examples/advanced-generic.dummy2",
+        "tags":["Other"],"summary":"Dummy","operationId":"_examples/advanced-generic.dummy2",
         "responses":{
           "204":{"description":"No Content"},
           "401":{
@@ -17,7 +17,7 @@
     },
     "/deeper-with-session/two":{
       "get":{
-        "summary":"Dummy","operationId":"_examples/advanced-generic.dummy3",
+        "tags":["Other"],"summary":"Dummy","operationId":"_examples/advanced-generic.dummy3",
         "responses":{
           "204":{"description":"No Content"},
           "401":{
@@ -30,7 +30,7 @@
     },
     "/error-response":{
       "get":{
-        "summary":"Declare Expected Errors",
+        "tags":["Response"],"summary":"Declare Expected Errors",
         "description":"This use case demonstrates documentation of expected errors.",
         "operationId":"_examples/advanced-generic.errorResponse",
         "parameters":[
@@ -67,7 +67,7 @@
     },
     "/file-multi-upload":{
       "post":{
-        "summary":"Files Uploads With 'multipart/form-data'",
+        "tags":["Request"],"summary":"Files Uploads With 'multipart/form-data'",
         "operationId":"_examples/advanced-generic.fileMultiUploader",
         "parameters":[
           {
@@ -87,7 +87,8 @@
     },
     "/file-upload":{
       "post":{
-        "summary":"File Upload With 'multipart/form-data'","operationId":"_examples/advanced-generic.fileUploader",
+        "tags":["Request"],"summary":"File Upload With 'multipart/form-data'",
+        "operationId":"_examples/advanced-generic.fileUploader",
         "parameters":[
           {
             "name":"in_query","in":"query","description":"Simple scalar value in query.",
@@ -103,7 +104,7 @@
     },
     "/form":{
       "post":{
-        "summary":"Request With Form",
+        "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.",
         "operationId":"_examples/advanced-generic.form",
         "parameters":[
@@ -123,7 +124,7 @@
     },
     "/gzip-pass-through":{
       "get":{
-        "summary":"Direct Gzip","operationId":"_examples/advanced-generic.directGzip",
+        "tags":["Response"],"summary":"Direct Gzip","operationId":"_examples/advanced-generic.directGzip",
         "parameters":[
           {
             "name":"plainStruct","in":"query","description":"Output plain structure instead of gzip container.",
@@ -143,7 +144,7 @@
         "x-forbid-unknown-query":true
       },
       "head":{
-        "summary":"Direct Gzip","operationId":"_examples/advanced-generic.directGzip2",
+        "tags":["Response"],"summary":"Direct Gzip","operationId":"_examples/advanced-generic.directGzip2",
         "parameters":[
           {
             "name":"plainStruct","in":"query","description":"Output plain structure instead of gzip container.",
@@ -165,8 +166,8 @@
     },
     "/html-response/{id}":{
       "get":{
-        "summary":"Request With HTML Response","description":"Request with templated HTML response.",
-        "operationId":"_examples/advanced-generic.htmlResponse",
+        "tags":["Response"],"summary":"Request With HTML Response",
+        "description":"Request with templated HTML response.","operationId":"_examples/advanced-generic.htmlResponse",
         "parameters":[
           {"name":"filter","in":"query","schema":{"type":"string"}},
           {"name":"id","in":"path","required":true,"schema":{"type":"integer"}},
@@ -183,7 +184,7 @@
     },
     "/json-body-manual/{in-path}":{
       "post":{
-        "summary":"Request With JSON Body and manual decoder",
+        "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.",
         "operationId":"_examples/advanced-generic.jsonBodyManual",
         "parameters":[
@@ -212,7 +213,7 @@
     },
     "/json-body-validation/{in-path}":{
       "post":{
-        "summary":"Request With JSON Body and non-trivial validation",
+        "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.",
         "operationId":"_examples/advanced-generic.jsonBodyValidation",
         "parameters":[
@@ -241,7 +242,7 @@
     },
     "/json-body/{in-path}":{
       "post":{
-        "summary":"Request With JSON Body",
+        "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.",
         "operationId":"_examples/advanced-generic.jsonBody",
         "parameters":[
@@ -270,8 +271,8 @@
     },
     "/json-map-body":{
       "post":{
-        "summary":"Request With JSON Map In Body","description":"Request with JSON object (map) body.",
-        "operationId":"_examples/advanced-generic.jsonMapBody",
+        "tags":["Request"],"summary":"Request With JSON Map In Body",
+        "description":"Request with JSON object (map) body.","operationId":"_examples/advanced-generic.jsonMapBody",
         "parameters":[
           {
             "name":"in_query","in":"query","description":"Simple scalar value in query.",
@@ -294,7 +295,7 @@
     },
     "/json-param/{in-path}":{
       "get":{
-        "summary":"Request With JSON Query Parameter",
+        "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.",
         "operationId":"_examples/advanced-generic.jsonParam",
         "parameters":[
@@ -330,7 +331,8 @@
     },
     "/json-slice-body":{
       "post":{
-        "summary":"Request With JSON Array In Body","operationId":"_examples/advanced-generic.jsonSliceBody",
+        "tags":["Request"],"summary":"Request With JSON Array In Body",
+        "operationId":"_examples/advanced-generic.jsonSliceBody",
         "parameters":[
           {
             "name":"in_query","in":"query","description":"Simple scalar value in query.",
@@ -353,7 +355,7 @@
     },
     "/no-validation":{
       "post":{
-        "summary":"No Validation","description":"Input/Output without validation.",
+        "tags":["Request","Response"],"summary":"No Validation","description":"Input/Output without validation.",
         "operationId":"_examples/advanced-generic.noValidation",
         "parameters":[
           {"name":"q","in":"query","schema":{"type":"boolean"}},
@@ -375,7 +377,7 @@
     },
     "/output-csv-writer":{
       "get":{
-        "summary":"Output With Stream Writer","description":"Output with stream writer.",
+        "tags":["Response"],"summary":"Output With Stream Writer","description":"Output with stream writer.",
         "operationId":"_examples/advanced-generic.outputCSVWriter",
         "responses":{
           "200":{
@@ -397,7 +399,7 @@
     },
     "/output-headers":{
       "get":{
-        "summary":"Output With Headers","description":"Output with headers.",
+        "tags":["Response"],"summary":"Output With Headers","description":"Output with headers.",
         "operationId":"_examples/advanced-generic.outputHeaders",
         "responses":{
           "200":{
@@ -413,7 +415,7 @@
         }
       },
       "head":{
-        "summary":"Output With Headers","description":"Output with headers.",
+        "tags":["Response"],"summary":"Output With Headers","description":"Output with headers.",
         "operationId":"_examples/advanced-generic.outputHeaders2",
         "responses":{
           "200":{
@@ -431,7 +433,8 @@
     },
     "/query-object":{
       "get":{
-        "summary":"Request With Object As Query Parameter","operationId":"_examples/advanced-generic.queryObject",
+        "tags":["Request"],"summary":"Request With Object As Query Parameter",
+        "operationId":"_examples/advanced-generic.queryObject",
         "parameters":[
           {
             "name":"in_query","in":"query","description":"Object value in query.","style":"deepObject","explode":true,
@@ -449,7 +452,7 @@
     },
     "/req-resp-mapping":{
       "post":{
-        "summary":"Request Response Mapping",
+        "tags":["Request","Response"],"summary":"Request Response Mapping",
         "description":"This use case has transport concerns fully decoupled with external req/resp mapping.",
         "operationId":"reqRespMapping",
         "parameters":[
@@ -480,7 +483,7 @@
     },
     "/root-with-session":{
       "get":{
-        "summary":"Dummy","operationId":"_examples/advanced-generic.dummy",
+        "tags":["Other"],"summary":"Dummy","operationId":"_examples/advanced-generic.dummy",
         "responses":{
           "204":{"description":"No Content"},
           "401":{
@@ -491,9 +494,48 @@
         "security":[{"User":[]}]
       }
     },
+    "/text-req-body-ptr/{path}":{
+      "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.",
+        "operationId":"_examples/advanced-generic.textReqBodyPtr",
+        "parameters":[
+          {"name":"query","in":"query","schema":{"type":"integer"}},
+          {"name":"path","in":"path","required":true,"schema":{"type":"string"}}
+        ],
+        "requestBody":{"content":{"text/csv":{"schema":{"type":"string"}}}},
+        "responses":{
+          "200":{
+            "description":"OK",
+            "content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedOutputType3"}}}
+          }
+        },
+        "x-forbid-unknown-path":true,"x-forbid-unknown-query":true
+      }
+    },
+    "/text-req-body/{path}":{
+      "post":{
+        "tags":["Request"],"summary":"Request With Text Body",
+        "description":"This usecase allows direct access to original `*http.Request` while keeping automated decoding of parameters.",
+        "operationId":"_examples/advanced-generic.textReqBody",
+        "parameters":[
+          {"name":"query","in":"query","schema":{"type":"integer"}},
+          {"name":"path","in":"path","required":true,"schema":{"type":"string"}}
+        ],
+        "requestBody":{"content":{"text/csv":{"schema":{"type":"string"}}}},
+        "responses":{
+          "200":{
+            "description":"OK",
+            "content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedOutputType2"}}}
+          }
+        },
+        "x-forbid-unknown-path":true,"x-forbid-unknown-query":true
+      }
+    },
     "/validation":{
       "post":{
-        "summary":"Validation","description":"Input/Output with validation. Custom annotation.",
+        "tags":["Request","Response","Validation"],"summary":"Validation",
+        "description":"Input/Output with validation. Custom annotation.",
         "operationId":"_examples/advanced-generic.validation",
         "parameters":[
           {
@@ -622,6 +664,8 @@
         "type":"object",
         "properties":{"inQuery":{"type":"object","additionalProperties":{"type":"number"},"nullable":true}}
       },
+      "AdvancedOutputType2":{"type":"object","properties":{"path":{"type":"string"},"query":{"type":"integer"},"text":{"type":"string"}}},
+      "AdvancedOutputType3":{"type":"object","properties":{"path":{"type":"string"},"query":{"type":"integer"},"text":{"type":"string"}}},
       "AdvancedOutputWithJSON":{
         "type":"object",
         "properties":{
diff --git a/nethttp/options_test.go b/nethttp/options_test.go
new file mode 100644
index 0000000..ed38066
--- /dev/null
+++ b/nethttp/options_test.go
@@ -0,0 +1,23 @@
+package nethttp_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"github.com/swaggest/assertjson"
+	"github.com/swaggest/openapi-go/openapi3"
+	"github.com/swaggest/rest/nethttp"
+)
+
+func TestRequestBodyContent(t *testing.T) {
+	h := &nethttp.Handler{}
+	op := openapi3.Operation{}
+
+	nethttp.RequestBodyContent("text/plain")(h)
+	require.Len(t, h.OperationAnnotations, 1)
+	require.NoError(t, h.OperationAnnotations[0](&op))
+	assertjson.EqualMarshal(t, []byte(`{
+	  "requestBody":{"content":{"text/plain":{"schema":{"type":"string"}}}},
+	  "responses":{}
+	}`), op)
+}
diff --git a/request/decoder_test.go b/request/decoder_test.go
index 7e0136f..f3f57a7 100644
--- a/request/decoder_test.go
+++ b/request/decoder_test.go
@@ -443,7 +443,7 @@ func (i *inputWithSetter) SetRequest(r *http.Request) {
 	i.r = r
 }
 
-func TestDecoder_Decode_manualLoader(t *testing.T) {
+func TestDecoder_Decode_manualLoader_ptr(t *testing.T) {
 	req, err := http.NewRequestWithContext(context.Background(), http.MethodGet,
 		"/?time=2020-04-04T00:00:00Z&date=2020-04-04", nil)
 	assert.NoError(t, err)
@@ -469,7 +469,33 @@ func TestDecoder_Decode_manualLoader(t *testing.T) {
 	assert.True(t, input.Time.IsZero())
 }
 
-func TestDecoder_Decode_setter(t *testing.T) {
+func TestDecoder_Decode_manualLoader_val(t *testing.T) {
+	req, err := http.NewRequestWithContext(context.Background(), http.MethodGet,
+		"/?time=2020-04-04T00:00:00Z&date=2020-04-04", nil)
+	assert.NoError(t, err)
+
+	input := inputWithLoader{}
+	loadTriggered := false
+
+	input.load = func(r *http.Request) error {
+		assert.Equal(t, "/?time=2020-04-04T00:00:00Z&date=2020-04-04", r.URL.String())
+
+		loadTriggered = true
+
+		return nil
+	}
+
+	dec := request.NewDecoderFactory().MakeDecoder(http.MethodGet, input, nil)
+	validator := jsonschema.NewFactory(&openapi.Collector{}, &openapi.Collector{}).
+		MakeRequestValidator(http.MethodGet, input, nil)
+
+	err = dec.Decode(req, &input, validator)
+	assert.NoError(t, err, fmt.Sprintf("%v", err))
+	assert.True(t, loadTriggered)
+	assert.True(t, input.Time.IsZero())
+}
+
+func TestDecoder_Decode_setter_ptr(t *testing.T) {
 	req, err := http.NewRequestWithContext(context.Background(), http.MethodGet,
 		"/?time=2020-04-04T00:00:00Z&date=2020-04-04", nil)
 	assert.NoError(t, err)
@@ -486,6 +512,23 @@ func TestDecoder_Decode_setter(t *testing.T) {
 	assert.NotNil(t, input.r)
 }
 
+func TestDecoder_Decode_setter_val(t *testing.T) {
+	req, err := http.NewRequestWithContext(context.Background(), http.MethodGet,
+		"/?time=2020-04-04T00:00:00Z&date=2020-04-04", nil)
+	assert.NoError(t, err)
+
+	input := inputWithSetter{}
+
+	dec := request.NewDecoderFactory().MakeDecoder(http.MethodGet, input, nil)
+	validator := jsonschema.NewFactory(&openapi.Collector{}, &openapi.Collector{}).
+		MakeRequestValidator(http.MethodGet, input, nil)
+
+	err = dec.Decode(req, &input, validator)
+	assert.NoError(t, err, fmt.Sprintf("%v", err))
+	assert.False(t, input.Time.IsZero())
+	assert.NotNil(t, input.r)
+}
+
 func TestDecoder_Decode_unknownParams(t *testing.T) {
 	req, err := http.NewRequestWithContext(context.Background(), http.MethodGet,
 		"/?foo=1&bar=1&bar=2&baz&quux=123", nil)