Repository: swaggest/rest
Pull request #33 (2021-04-29 14:14:19)

Add async expectations to server mock

github-actions[bot]: ### Unit Test Coverage
total:	(statements)	81.0%
<details><summary>Coverage diff with base branch</summary>

```diff
93c93
< github.com/swaggest/rest/request/decoder.go	formDataToURLValues	66.7%
---
> github.com/swaggest/rest/request/decoder.go	formDataToURLValues	77.8%
159a160
> github.com/swaggest/rest/resttest/server.go	ExpectAsync	100.0%
163c164,165
< github.com/swaggest/rest/resttest/server.go	ServeHTTP	72.7%
---
> github.com/swaggest/rest/resttest/server.go	checkAsync	68.8%
> github.com/swaggest/rest/resttest/server.go	ServeHTTP	75.0%
167c169
< github.com/swaggest/rest/resttest/server.go	ExpectationsWereMet	83.3%
---
> github.com/swaggest/rest/resttest/server.go	ExpectationsWereMet	72.2%
176c178
< total:	(statements)	81.2%
---
> total:	(statements)	81.0%
```
</details>
<!-- Sticky Pull Request Commentunit-test -->
codecov[bot]: # [Codecov](https://codecov.io/gh/swaggest/rest/pull/33?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest) Report
> Merging [#33](https://codecov.io/gh/swaggest/rest/pull/33?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest) (7f99dfc) into [master](https://codecov.io/gh/swaggest/rest/commit/862b35204d84e37bbb65df6edf45288312e21054?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest) (862b352) will **increase** coverage by `0.11%`.
> The diff coverage is `43.47%`.

[![Impacted file tree graph](https://codecov.io/gh/swaggest/rest/pull/33/graphs/tree.svg?width=650&height=150&src=pr&token=OpLHxxPdmn&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest)](https://codecov.io/gh/swaggest/rest/pull/33?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest)

```diff
@@            Coverage Diff             @@
##           master      #33      +/-   ##
==========================================
+ Coverage   73.27%   73.39%   +0.11%     
==========================================
  Files          26       26              
  Lines        1508     1477      -31     
==========================================
- Hits         1105     1084      -21     
+ Misses        254      251       -3     
+ Partials      149      142       -7     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `73.39% <43.47%> (+0.11%)` | :arrow_up: |

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/33?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest) | Coverage Δ | |
|---|---|---|
| [request/decoder.go](https://codecov.io/gh/swaggest/rest/pull/33/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest#diff-cmVxdWVzdC9kZWNvZGVyLmdv) | `77.14% <ø> (ø)` | |
| [request/factory.go](https://codecov.io/gh/swaggest/rest/pull/33/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest#diff-cmVxdWVzdC9mYWN0b3J5Lmdv) | `82.35% <ø> (ø)` | |
| [response/encoder.go](https://codecov.io/gh/swaggest/rest/pull/33/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest#diff-cmVzcG9uc2UvZW5jb2Rlci5nbw==) | `63.58% <ø> (ø)` | |
| [resttest/server.go](https://codecov.io/gh/swaggest/rest/pull/33/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest#diff-cmVzdHRlc3Qvc2VydmVyLmdv) | `61.03% <43.47%> (-1.93%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/swaggest/rest/pull/33?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/swaggest/rest/pull/33?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest). Last update [862b352...7f99dfc](https://codecov.io/gh/swaggest/rest/pull/33?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?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.65µs ± 2%    1.65µs ± 1%    ~     (p=0.743 n=4+4)
pkg:github.com/swaggest/rest/request goos:linux goarch:amd64
Decoder_Decode-2                           936ns ± 3%     945ns ± 1%    ~     (p=0.222 n=5+5)
DecoderFunc_Decode-2                      2.58µs ± 2%    2.56µs ± 2%    ~     (p=0.421 n=5+5)
Decoder_Decode_json-2                     29.7µs ± 1%    28.5µs ± 1%  -3.80%  (p=0.008 n=5+5)
Decoder_Decode_queryObject-2              6.76µs ± 2%    6.70µs ± 2%    ~     (p=0.421 n=5+5)
Decoder_Decode_jsonParam-2                2.36µs ± 2%    2.35µs ± 1%    ~     (p=0.500 n=5+5)
DecoderFactory_SetDecoderFunc-2           2.07µs ± 1%    2.11µs ± 1%  +1.99%  (p=0.032 n=5+5)
pkg:github.com/swaggest/rest/response/gzip goos:linux goarch:amd64
Middleware-2                              14.7µs ± 1%    14.7µs ± 1%    ~     (p=1.000 n=5+5)
Middleware_control-2                      3.97µs ± 2%    3.97µs ± 2%    ~     (p=1.000 n=5+5)

name                                    old alloc/op   new alloc/op   delta
pkg:github.com/swaggest/rest/jsonschema goos:linux goarch:amd64
RequestValidator_ValidateRequestData-2    2.50kB ± 0%    2.50kB ± 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.3kB ± 0%    12.3kB ± 0%    ~     (all equal)
Decoder_Decode_queryObject-2              2.00kB ± 0%    2.00kB ± 0%    ~     (all equal)
Decoder_Decode_jsonParam-2                  720B ± 0%      720B ± 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.11kB ± 2%    1.13kB ± 1%  +1.65%  (p=0.048 n=5+5)
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                        169 ± 0%       169 ± 0%    ~     (all equal)
Decoder_Decode_queryObject-2                36.0 ± 0%      36.0 ± 0%    ~     (all equal)
Decoder_Decode_jsonParam-2                  12.0 ± 0%      12.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>
<!-- Sticky Pull Request Commentbench -->

Patch:
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
index 2cc5df3..b51abbb 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -14,10 +14,10 @@ jobs:
     steps:
       - uses: actions/checkout@v2
       - name: golangci-lint
-        uses: golangci/golangci-lint-action@v2.5.1
+        uses: golangci/golangci-lint-action@v2.5.2
         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.38.0
+          version: v1.39.0
 
           # Optional: working directory, useful for monorepos
           # working-directory: somedir
diff --git a/.gitignore b/.gitignore
index ae81169..ed77e49 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,6 @@
-.idea
-coverage.txt
-unit.coverprofile
 *_last_run.json
-.vscode
-bench-*.txt
-vendor
\ No newline at end of file
+/.idea
+/*.coverprofile
+/.vscode
+/bench-*.txt
+/vendor
diff --git a/.golangci.yml b/.golangci.yml
index 55751fd..3bb5204 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,7 +1,6 @@
 # See https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml
 run:
   tests: true
-  deadline: 5m
 
 linters-settings:
   errcheck:
@@ -21,18 +20,20 @@ linters-settings:
 linters:
   enable-all: true
   disable:
-    - gochecknoglobals
-    - testpackage
     - goerr113
+    - cyclop
+    - lll
+    - maligned
+    - gochecknoglobals
+    - gomnd
     - wrapcheck
     - paralleltest
     - forbidigo
     - exhaustivestruct
-    - ifshort
-    - interfacer
-    - maligned
+    - interfacer # deprecated
     - forcetypeassert
-    - cyclop # TODO review issues.
+    - scopelint # deprecated
+    - ifshort # too many false positives
 
 issues:
   exclude-use-default: false
@@ -43,4 +44,6 @@ issues:
         - goerr113
         - noctx
         - funlen
+        - dupl
       path: "_test.go"
+
diff --git a/Makefile b/Makefile
index 4be4f3f..44ea5c9 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,4 @@
-BENCH_COUNT ?= 5
-REF_NAME ?= $(shell git symbolic-ref HEAD --short | tr / - 2>/dev/null)
+#GOLANGCI_LINT_VERSION := "v1.39.0" # Optional configuration to pinpoint golangci-lint version.
 
 # The head of Makefile determines location of dev-go to include standard targets.
 GO ?= go
@@ -34,17 +33,7 @@ endif
 -include $(DEVGO_PATH)/makefiles/bench.mk
 -include $(DEVGO_PATH)/makefiles/reset-ci.mk
 
-## Run tests
-test: test-unit test-examples
-
-test-examples:
-	cd _examples && go test -race ./...
+# Add your custom targets here.
 
-## Run benchmark for app examples, iterations count controlled by BENCH_COUNT, default 5.
-bench-run-examples:
-	@cd _examples && $(GO) test -bench=. -count=$(BENCH_COUNT) -run=^a  ./... >../bench-examples-$(REF_NAME).txt
-
-## Show result of benchmark for app examples.
-bench-stat-examples:
-	@test -s $(GOPATH)/bin/benchstat || GO111MODULE=off GOFLAGS= GOBIN=$(GOPATH)/bin $(GO) get -u golang.org/x/perf/cmd/benchstat
-	@test -s bench-examples-master.txt && benchstat bench-examples-master.txt bench-examples-$(REF_NAME).txt || benchstat bench-examples-$(REF_NAME).txt
+## Run tests
+test: test-unit
diff --git a/_examples/go.mod b/_examples/go.mod
index a48edcb..b0d6b26 100644
--- a/_examples/go.mod
+++ b/_examples/go.mod
@@ -5,14 +5,14 @@ go 1.13
 replace github.com/swaggest/rest => ../
 
 require (
-	github.com/bool64/dev v0.1.25
+	github.com/bool64/dev v0.1.27
 	github.com/bool64/httptestbench v0.1.1
 	github.com/go-chi/chi/v5 v5.0.2
 	github.com/kelseyhightower/envconfig v1.4.0
 	github.com/klauspost/compress v1.11.12 // indirect
 	github.com/stretchr/testify v1.7.0
 	github.com/swaggest/assertjson v1.6.4
-	github.com/swaggest/jsonschema-go v0.3.18
+	github.com/swaggest/jsonschema-go v0.3.19
 	github.com/swaggest/openapi-go v0.2.9
 	github.com/swaggest/rest v0.1.17
 	github.com/swaggest/swgui v1.2.1
diff --git a/_examples/go.sum b/_examples/go.sum
index c2e0466..b2a0aeb 100644
--- a/_examples/go.sum
+++ b/_examples/go.sum
@@ -8,14 +8,13 @@ github.com/bool64/dev v0.1.10/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbs
 github.com/bool64/dev v0.1.12/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbscdg=
 github.com/bool64/dev v0.1.17/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
 github.com/bool64/dev v0.1.18/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
-github.com/bool64/dev v0.1.24/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
-github.com/bool64/dev v0.1.25 h1:iSfcaS1FP6F2TlMI1daAvqk6PYl23XDf6oZu7WRXB8Y=
 github.com/bool64/dev v0.1.25/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
+github.com/bool64/dev v0.1.27 h1:Cx4g/QLtVVmmfKZfyxAfOGKqgT86tqUbFB1vRN6JbfM=
+github.com/bool64/dev v0.1.27/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
 github.com/bool64/httptestbench v0.1.1 h1:08112fXc5F1XuTWLdC9lPGB4FSWNACoeUWTeoR0a2qA=
 github.com/bool64/httptestbench v0.1.1/go.mod h1:gSmfFacu85Je3u6ZjmLZsFolcv7cwg1fLNUCzq2UUsM=
 github.com/bool64/shared v0.1.0/go.mod h1:khzw051JtnOZH6lX21rQ+sqbeC0FFEcU1wfchRS7EyU=
 github.com/bool64/shared v0.1.1/go.mod h1:khzw051JtnOZH6lX21rQ+sqbeC0FFEcU1wfchRS7EyU=
-github.com/bool64/shared v0.1.2/go.mod h1:Yt5dVKw6njpWoIN3rRV9mgkZO4CExiQUw9Q8n/Vovmo=
 github.com/bool64/shared v0.1.3 h1:gj7XZPYa1flQsCg3q9AIju+W2A1jaexK0fdFu2XtaG0=
 github.com/bool64/shared v0.1.3/go.mod h1:RF1p1Oi29ofgOvinBpetbF5mceOUP3kpMkvLbWOmtm0=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -56,7 +55,6 @@ github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/iancoleman/orderedmap v0.1.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
-github.com/iancoleman/orderedmap v0.1.2/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
 github.com/iancoleman/orderedmap v0.2.0 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6jq8pNA=
 github.com/iancoleman/orderedmap v0.2.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
@@ -110,9 +108,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
 github.com/santhosh-tekuri/jsonschema/v2 v2.2.0 h1:72xCpK0g27Y1is2lreGNcZhIX3ZCtRpkHvvHrHD+5y4=
 github.com/santhosh-tekuri/jsonschema/v2 v2.2.0/go.mod h1:yzJzKUGV4RbWqWIBBP4wSOBqavX5saE02yirLS0OTyg=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
-github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
-github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
 github.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0 h1:mj/nMDAwTBiaCqMEs4cYCqF7pO6Np7vhy1D1wcQGz+E=
 github.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
@@ -126,17 +123,16 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/swaggest/assertjson v1.0.0/go.mod h1:mE5ltBbrB+Ya8Xar5OMITxya76vwLZMoPG8aXF7sVQc=
 github.com/swaggest/assertjson v1.4.1/go.mod h1:v7atNMMlIa5ry2H1rgCHhvz7eho8yfP85UGio0+TFEI=
 github.com/swaggest/assertjson v1.6.0/go.mod h1:2dZU3TJoGcH+QPrekon0+zG5hJnZUpdEAxiLoxmJALI=
-github.com/swaggest/assertjson v1.6.3/go.mod h1:fShyZuy4F57C1smqdYNVYDyehECSJLmvYiCGwWyQEKo=
 github.com/swaggest/assertjson v1.6.4 h1:SZy0H6Dyi+2Jn1PfKBFk+uwQ+3FcwtPthEpcpV84xTg=
 github.com/swaggest/assertjson v1.6.4/go.mod h1:HAXV18oavcbNHwN6wvfzYVrLuAoi3/mJ6sN0ZgNMX9U=
-github.com/swaggest/form v1.11.2 h1:DN3jhIh7PaJjOirfWq/DL6nbuXcjdVPazmtez3Bcy4w=
-github.com/swaggest/form v1.11.2/go.mod h1:nMN5aLiSG/Yg0YVbhXuqUKpMWc6MngThr/anrHRKuyk=
+github.com/swaggest/form/v5 v5.0.0 h1:dPCt04lJ5O/R9VvoClMaE7yBn9QivXo4E/T0nyZhLfA=
+github.com/swaggest/form/v5 v5.0.0/go.mod h1:iBeFldvqZEU/Na/xI3TdY68LuTqA56T7B1ylKVxva0A=
 github.com/swaggest/jsonschema-go v0.2.1/go.mod h1:QFauBdPTrU1UltwocM5FzOWnVjVVtcWkJWG3NlK9sV0=
 github.com/swaggest/jsonschema-go v0.2.4/go.mod h1:m4VV88Gbi7lCrt9ckJzigK1rMlEeFjdZUkJr1o5MnDE=
 github.com/swaggest/jsonschema-go v0.3.7/go.mod h1:TrWgbug4p2ZgcxnHDz+CvYvEtJ5KckL/XOV4mSR6FGw=
 github.com/swaggest/jsonschema-go v0.3.13/go.mod h1:INO3Dt0DeyNCbQbqMXNpMQ489BcyRtSb/k62OfmHbpY=
-github.com/swaggest/jsonschema-go v0.3.18 h1:LEi3qAnJgtWX4OKa23gLwW3xBwPhTSaTl2P4o7UcP3E=
-github.com/swaggest/jsonschema-go v0.3.18/go.mod h1:dWtjd/xXQVRdijf4LwHySCzylyEzJamIdwkKKKW0KkQ=
+github.com/swaggest/jsonschema-go v0.3.19 h1:Fa3R5ttSkmqTt1s124JGvxj1zxtngJExplTn3DTunEk=
+github.com/swaggest/jsonschema-go v0.3.19/go.mod h1:NQCceV7I4/UuAy0IwCaDzgyN9ODl9F1s6/egrE2pC3U=
 github.com/swaggest/openapi-go v0.1.3/go.mod h1:Zx4ZgJ7XvlFH9wCOHE7u8RAjLfiHAnCHeaD5kUDujVM=
 github.com/swaggest/openapi-go v0.1.13/go.mod h1:fju3Ka5zb8qBKf4789zLNVUqITydWLDjtPloZhhYHL8=
 github.com/swaggest/openapi-go v0.2.9 h1:JJQrDSRtKMycIkLju97yjhJbw72mqhsCDJjqFB8HvSs=
diff --git a/go.mod b/go.mod
index c9694f9..2810d78 100644
--- a/go.mod
+++ b/go.mod
@@ -3,16 +3,15 @@ module github.com/swaggest/rest
 go 1.13
 
 require (
-	github.com/bool64/dev v0.1.25
+	github.com/bool64/dev v0.1.27
 	github.com/bool64/shared v0.1.3
 	github.com/cespare/xxhash/v2 v2.1.1
 	github.com/go-chi/chi/v5 v5.0.2
 	github.com/santhosh-tekuri/jsonschema/v2 v2.2.0
-	github.com/sergi/go-diff v1.2.0 // indirect
 	github.com/stretchr/testify v1.7.0
 	github.com/swaggest/assertjson v1.6.4
-	github.com/swaggest/form v1.11.2
-	github.com/swaggest/jsonschema-go v0.3.18
+	github.com/swaggest/form/v5 v5.0.0
+	github.com/swaggest/jsonschema-go v0.3.19
 	github.com/swaggest/openapi-go v0.2.9
 	github.com/swaggest/refl v0.1.7
 	github.com/swaggest/usecase v0.1.5
diff --git a/go.sum b/go.sum
index 4deaf37..4187666 100644
--- a/go.sum
+++ b/go.sum
@@ -4,12 +4,11 @@ github.com/bool64/dev v0.1.7/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbsc
 github.com/bool64/dev v0.1.10/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbscdg=
 github.com/bool64/dev v0.1.12/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbscdg=
 github.com/bool64/dev v0.1.17/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
-github.com/bool64/dev v0.1.24/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
-github.com/bool64/dev v0.1.25 h1:iSfcaS1FP6F2TlMI1daAvqk6PYl23XDf6oZu7WRXB8Y=
 github.com/bool64/dev v0.1.25/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
+github.com/bool64/dev v0.1.27 h1:Cx4g/QLtVVmmfKZfyxAfOGKqgT86tqUbFB1vRN6JbfM=
+github.com/bool64/dev v0.1.27/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
 github.com/bool64/shared v0.1.0/go.mod h1:khzw051JtnOZH6lX21rQ+sqbeC0FFEcU1wfchRS7EyU=
 github.com/bool64/shared v0.1.1/go.mod h1:khzw051JtnOZH6lX21rQ+sqbeC0FFEcU1wfchRS7EyU=
-github.com/bool64/shared v0.1.2/go.mod h1:Yt5dVKw6njpWoIN3rRV9mgkZO4CExiQUw9Q8n/Vovmo=
 github.com/bool64/shared v0.1.3 h1:gj7XZPYa1flQsCg3q9AIju+W2A1jaexK0fdFu2XtaG0=
 github.com/bool64/shared v0.1.3/go.mod h1:RF1p1Oi29ofgOvinBpetbF5mceOUP3kpMkvLbWOmtm0=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -50,7 +49,6 @@ github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/iancoleman/orderedmap v0.1.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
-github.com/iancoleman/orderedmap v0.1.2/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
 github.com/iancoleman/orderedmap v0.2.0 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6jq8pNA=
 github.com/iancoleman/orderedmap v0.2.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
@@ -97,9 +95,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
 github.com/santhosh-tekuri/jsonschema/v2 v2.2.0 h1:72xCpK0g27Y1is2lreGNcZhIX3ZCtRpkHvvHrHD+5y4=
 github.com/santhosh-tekuri/jsonschema/v2 v2.2.0/go.mod h1:yzJzKUGV4RbWqWIBBP4wSOBqavX5saE02yirLS0OTyg=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
-github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
-github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@@ -109,17 +106,16 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/swaggest/assertjson v1.0.0/go.mod h1:mE5ltBbrB+Ya8Xar5OMITxya76vwLZMoPG8aXF7sVQc=
 github.com/swaggest/assertjson v1.4.1/go.mod h1:v7atNMMlIa5ry2H1rgCHhvz7eho8yfP85UGio0+TFEI=
 github.com/swaggest/assertjson v1.6.0/go.mod h1:2dZU3TJoGcH+QPrekon0+zG5hJnZUpdEAxiLoxmJALI=
-github.com/swaggest/assertjson v1.6.3/go.mod h1:fShyZuy4F57C1smqdYNVYDyehECSJLmvYiCGwWyQEKo=
 github.com/swaggest/assertjson v1.6.4 h1:SZy0H6Dyi+2Jn1PfKBFk+uwQ+3FcwtPthEpcpV84xTg=
 github.com/swaggest/assertjson v1.6.4/go.mod h1:HAXV18oavcbNHwN6wvfzYVrLuAoi3/mJ6sN0ZgNMX9U=
-github.com/swaggest/form v1.11.2 h1:DN3jhIh7PaJjOirfWq/DL6nbuXcjdVPazmtez3Bcy4w=
-github.com/swaggest/form v1.11.2/go.mod h1:nMN5aLiSG/Yg0YVbhXuqUKpMWc6MngThr/anrHRKuyk=
+github.com/swaggest/form/v5 v5.0.0 h1:dPCt04lJ5O/R9VvoClMaE7yBn9QivXo4E/T0nyZhLfA=
+github.com/swaggest/form/v5 v5.0.0/go.mod h1:iBeFldvqZEU/Na/xI3TdY68LuTqA56T7B1ylKVxva0A=
 github.com/swaggest/jsonschema-go v0.2.1/go.mod h1:QFauBdPTrU1UltwocM5FzOWnVjVVtcWkJWG3NlK9sV0=
 github.com/swaggest/jsonschema-go v0.2.4/go.mod h1:m4VV88Gbi7lCrt9ckJzigK1rMlEeFjdZUkJr1o5MnDE=
 github.com/swaggest/jsonschema-go v0.3.7/go.mod h1:TrWgbug4p2ZgcxnHDz+CvYvEtJ5KckL/XOV4mSR6FGw=
 github.com/swaggest/jsonschema-go v0.3.13/go.mod h1:INO3Dt0DeyNCbQbqMXNpMQ489BcyRtSb/k62OfmHbpY=
-github.com/swaggest/jsonschema-go v0.3.18 h1:LEi3qAnJgtWX4OKa23gLwW3xBwPhTSaTl2P4o7UcP3E=
-github.com/swaggest/jsonschema-go v0.3.18/go.mod h1:dWtjd/xXQVRdijf4LwHySCzylyEzJamIdwkKKKW0KkQ=
+github.com/swaggest/jsonschema-go v0.3.19 h1:Fa3R5ttSkmqTt1s124JGvxj1zxtngJExplTn3DTunEk=
+github.com/swaggest/jsonschema-go v0.3.19/go.mod h1:NQCceV7I4/UuAy0IwCaDzgyN9ODl9F1s6/egrE2pC3U=
 github.com/swaggest/openapi-go v0.1.3/go.mod h1:Zx4ZgJ7XvlFH9wCOHE7u8RAjLfiHAnCHeaD5kUDujVM=
 github.com/swaggest/openapi-go v0.1.13/go.mod h1:fju3Ka5zb8qBKf4789zLNVUqITydWLDjtPloZhhYHL8=
 github.com/swaggest/openapi-go v0.2.9 h1:JJQrDSRtKMycIkLju97yjhJbw72mqhsCDJjqFB8HvSs=
diff --git a/request/decoder.go b/request/decoder.go
index 685fbbb..d3f1c1a 100644
--- a/request/decoder.go
+++ b/request/decoder.go
@@ -5,7 +5,7 @@ import (
 	"net/url"
 	"strings"
 
-	"github.com/swaggest/form"
+	"github.com/swaggest/form/v5"
 	"github.com/swaggest/rest"
 	"github.com/swaggest/rest/nethttp"
 )
diff --git a/request/factory.go b/request/factory.go
index 0b8a2e3..c180182 100644
--- a/request/factory.go
+++ b/request/factory.go
@@ -9,7 +9,7 @@ import (
 	"reflect"
 	"strings"
 
-	"github.com/swaggest/form"
+	"github.com/swaggest/form/v5"
 	"github.com/swaggest/openapi-go/openapi3"
 	"github.com/swaggest/refl"
 	"github.com/swaggest/rest"
diff --git a/response/encoder.go b/response/encoder.go
index 7d203dd..032dc65 100644
--- a/response/encoder.go
+++ b/response/encoder.go
@@ -9,7 +9,7 @@ import (
 	"strconv"
 	"sync"
 
-	"github.com/swaggest/form"
+	"github.com/swaggest/form/v5"
 	"github.com/swaggest/refl"
 	"github.com/swaggest/rest"
 	"github.com/swaggest/usecase"


Test patch:
diff --git a/request/jsonbody_test.go b/request/jsonbody_test.go
index 9296195..14db399 100644
--- a/request/jsonbody_test.go
+++ b/request/jsonbody_test.go
@@ -1,4 +1,4 @@
-package request
+package request // nolint:testpackage
 
 import (
 	"bytes"
diff --git a/resttest/server.go b/resttest/server.go
index 9d53cce..f173d83 100644
--- a/resttest/server.go
+++ b/resttest/server.go
@@ -49,6 +49,7 @@ type ServerMock struct {
 	mu           sync.Mutex
 	server       *httptest.Server
 	expectations []Expectation
+	async        []Expectation
 }
 
 // NewServerMock creates mocked server.
@@ -68,6 +69,18 @@ func (sm *ServerMock) Expect(e Expectation) {
 	sm.expectations = append(sm.expectations, e)
 }
 
+// ExpectAsync sets non-sequential expectation.
+//
+// Asynchronous expectations are checked for every incoming request,
+// first match is used for response.
+// If there are no matches, regular (sequential expectations are used).
+func (sm *ServerMock) ExpectAsync(e Expectation) {
+	sm.mu.Lock()
+	defer sm.mu.Unlock()
+
+	sm.async = append(sm.async, e)
+}
+
 // Close closes mocked server.
 func (sm *ServerMock) Close() {
 	sm.server.Close()
@@ -125,11 +138,48 @@ func (sm *ServerMock) writeResponse(rw http.ResponseWriter, expectation Expectat
 	return !sm.checkFail(rw, err)
 }
 
+func (sm *ServerMock) checkAsync(rw http.ResponseWriter, req *http.Request) bool {
+	for i, expectation := range sm.async {
+		if err := sm.checkRequest(req, expectation); err != nil {
+			continue
+		}
+
+		if !sm.writeResponse(rw, expectation) {
+			return true
+		}
+
+		if expectation.Unlimited {
+			return true
+		}
+
+		if expectation.Repeated > 0 {
+			expectation.Repeated--
+			sm.async[i] = expectation
+
+			if expectation.Repeated > 0 {
+				return true
+			}
+		}
+
+		// Deleting expectation.
+		sm.async[i] = sm.async[len(sm.async)-1]
+		sm.async = sm.async[:len(sm.async)-1]
+
+		return true
+	}
+
+	return false
+}
+
 // ServeHTTP asserts request expectations and serves mocked response.
 func (sm *ServerMock) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 	sm.mu.Lock()
 	defer sm.mu.Unlock()
 
+	if sm.checkAsync(rw, req) {
+		return
+	}
+
 	if len(sm.expectations) == 0 {
 		body, err := ioutil.ReadAll(req.Body)
 		if err == nil && len(body) > 0 {
@@ -263,21 +313,33 @@ func (sm *ServerMock) ExpectationsWereMet() error {
 	sm.mu.Lock()
 	defer sm.mu.Unlock()
 
-	if len(sm.expectations) != 0 {
-		if len(sm.expectations) == 1 && sm.expectations[0].Unlimited {
-			return nil
+	var unmet string
+
+	for _, e := range sm.expectations {
+		if e.Unlimited {
+			continue
 		}
 
-		unmet := ""
+		if e.Method != "" || e.RequestURI != "" {
+			unmet += ", " + e.Method + " " + e.RequestURI
+		} else {
+			unmet += ", response " + string(e.ResponseBody)
+		}
+	}
 
-		for _, e := range sm.expectations {
-			if e.Method != "" || e.RequestURI != "" {
-				unmet += ", " + e.Method + " " + e.RequestURI
-			} else {
-				unmet += ", response " + string(e.ResponseBody)
-			}
+	for _, e := range sm.async {
+		if e.Unlimited {
+			continue
 		}
 
+		if e.Method != "" || e.RequestURI != "" {
+			unmet += ", " + e.Method + " " + e.RequestURI
+		} else {
+			unmet += ", response " + string(e.ResponseBody)
+		}
+	}
+
+	if unmet != "" {
 		return errors.New("there are remaining expectations that were not met: " + unmet[2:])
 	}
 
diff --git a/resttest/server_test.go b/resttest/server_test.go
index e1d7778..f704426 100644
--- a/resttest/server_test.go
+++ b/resttest/server_test.go
@@ -265,3 +265,67 @@ func TestServerMock_vars(t *testing.T) {
 
 	assert.Equal(t, `{"bar":"foo","dynEcho":"abc"}`, string(body))
 }
+
+func TestServerMock_ExpectAsync(t *testing.T) {
+	sm, url := resttest.NewServerMock()
+	sm.Expect(resttest.Expectation{
+		Method:       http.MethodGet,
+		RequestURI:   "/",
+		ResponseBody: []byte(`{"bar":"foo"}`),
+	})
+	sm.ExpectAsync(resttest.Expectation{
+		Method:       http.MethodGet,
+		RequestURI:   "/async1",
+		ResponseBody: []byte(`{"bar":"async1"}`),
+	})
+	sm.ExpectAsync(resttest.Expectation{
+		Method:       http.MethodGet,
+		RequestURI:   "/async2",
+		ResponseBody: []byte(`{"bar":"async2"}`),
+		Unlimited:    true,
+	})
+
+	go func() {
+		req, err := http.NewRequest(http.MethodGet, url+"/async1", nil)
+		require.NoError(t, err)
+
+		resp, err := http.DefaultTransport.RoundTrip(req)
+		require.NoError(t, err)
+
+		body, err := ioutil.ReadAll(resp.Body)
+		require.NoError(t, err)
+
+		require.NoError(t, resp.Body.Close())
+		assert.Equal(t, `{"bar":"async1"}`, string(body))
+	}()
+
+	go func() {
+		for i := 0; i < 50; i++ {
+			req, err := http.NewRequest(http.MethodGet, url+"/async2", nil)
+			require.NoError(t, err)
+
+			resp, err := http.DefaultTransport.RoundTrip(req)
+			require.NoError(t, err)
+
+			body, err := ioutil.ReadAll(resp.Body)
+			require.NoError(t, err)
+
+			require.NoError(t, resp.Body.Close())
+			assert.Equal(t, `{"bar":"async2"}`, string(body))
+		}
+	}()
+
+	req, err := http.NewRequest(http.MethodGet, url+"/", nil)
+	require.NoError(t, err)
+
+	resp, err := http.DefaultTransport.RoundTrip(req)
+	require.NoError(t, err)
+
+	body, err := ioutil.ReadAll(resp.Body)
+	require.NoError(t, err)
+
+	require.NoError(t, resp.Body.Close())
+	assert.Equal(t, `{"bar":"foo"}`, string(body))
+
+	assert.NoError(t, sm.ExpectationsWereMet())
+}