Repository: swaggest/rest
Pull request #223 (2025-02-15 01:27:24)

Fix decoding of multipart form with scalars and files
Fixes https://github.com/swaggest/rest/issues/221.

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

| Language  | Files |   Lines   |   Code    | Comments | Blanks | Complexity |     Bytes     |
|-----------|-------|-----------|-----------|----------|--------|------------|---------------|
| Go        | 114   | 9000      | 6501      | 673      | 1826   | 1071 (-1)  | 219.8K (+18B) |
| Go (test) | 56    | 6942 (+5) | 5317 (+5) | 295      | 1330   | 196        | 189.3K (+89B) |
<!-- Sticky Pull Request CommentLOC -->
github-actions[bot]: ### Go API Changes

<pre>
# summary
Inferred base version: v0.2.71
Suggested version: v0.2.72
</pre>
<!-- Sticky Pull Request Commentgorelease -->
github-actions[bot]: ### Unit Test Coverage
total:								(statements)			80.7%
changed lines: (statements) 100.0%
<details><summary>Coverage of changed lines</summary>

|         File          |  Function   | Coverage |
|-----------------------|-------------|----------|
| Total                 |             | 100.0%   |
| request/decoder.go    |             | 100.0%   |
| request/decoder.go:79 | makeDecoder | 100.0%   |

</details>

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

|        File        |      Function       | Base Coverage | Current Coverage |
|--------------------|---------------------|---------------|------------------|
| Total              |                     | 80.5%         | 80.7% (+0.2%)    |
| request/decoder.go | formDataToURLValues | 55.6%         | 77.8% (+22.2%)   |

</details>
<!-- Sticky Pull Request Commentunit-test -->
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-4    1.14µs ± 4%    1.18µs ± 0%    ~     (p=0.135 n=5+5)
pkg:github.com/swaggest/rest/request goos:linux goarch:amd64
Decoder_Decode-4                           617ns ± 4%     608ns ± 4%    ~     (p=0.095 n=5+5)
DecoderFunc_Decode-4                      1.73µs ± 1%    1.70µs ± 1%  -1.86%  (p=0.008 n=5+5)
Decoder_Decode_json-4                     18.7µs ± 1%    18.5µs ± 1%    ~     (p=0.095 n=5+5)
Decoder_Decode_queryObject-4              3.99µs ± 1%    3.99µs ± 1%    ~     (p=0.500 n=5+5)
Decoder_Decode_jsonParam-4                1.54µs ± 0%    1.56µs ± 0%  +1.42%  (p=0.008 n=5+5)
DecoderFactory_SetDecoderFunc-4           1.36µs ± 1%    1.34µs ± 0%  -1.12%  (p=0.008 n=5+5)
pkg:github.com/swaggest/rest/response/gzip goos:linux goarch:amd64
Middleware-4                              11.3µs ± 2%    11.3µs ± 1%    ~     (p=0.841 n=5+5)
Middleware_control-4                      2.73µs ± 1%    2.71µs ± 0%    ~     (p=0.286 n=5+5)

name                                    old alloc/op   new alloc/op   delta
pkg:github.com/swaggest/rest/jsonschema goos:linux goarch:amd64
RequestValidator_ValidateRequestData-4    2.46kB ± 0%    2.46kB ± 0%    ~     (all equal)
pkg:github.com/swaggest/rest/request goos:linux goarch:amd64
Decoder_Decode-4                            440B ± 0%      440B ± 0%    ~     (all equal)
DecoderFunc_Decode-4                      1.53kB ± 0%    1.53kB ± 0%    ~     (all equal)
Decoder_Decode_json-4                     12.4kB ± 0%    12.4kB ± 0%    ~     (all equal)
Decoder_Decode_queryObject-4              2.01kB ± 0%    2.01kB ± 0%    ~     (all equal)
Decoder_Decode_jsonParam-4                  736B ± 0%      736B ± 0%    ~     (all equal)
DecoderFactory_SetDecoderFunc-4           1.02kB ± 0%    1.02kB ± 0%    ~     (all equal)
pkg:github.com/swaggest/rest/response/gzip goos:linux goarch:amd64
Middleware-4                              1.16kB ± 2%    1.16kB ± 4%    ~     (p=0.968 n=5+5)
Middleware_control-4                      11.1kB ± 0%    11.1kB ± 0%    ~     (all equal)

name                                    old allocs/op  new allocs/op  delta
pkg:github.com/swaggest/rest/jsonschema goos:linux goarch:amd64
RequestValidator_ValidateRequestData-4      8.00 ± 0%      8.00 ± 0%    ~     (all equal)
pkg:github.com/swaggest/rest/request goos:linux goarch:amd64
Decoder_Decode-4                            4.00 ± 0%      4.00 ± 0%    ~     (all equal)
DecoderFunc_Decode-4                        12.0 ± 0%      12.0 ± 0%    ~     (all equal)
Decoder_Decode_json-4                        177 ± 0%       177 ± 0%    ~     (all equal)
Decoder_Decode_queryObject-4                38.0 ± 0%      38.0 ± 0%    ~     (all equal)
Decoder_Decode_jsonParam-4                  12.0 ± 0%      12.0 ± 0%    ~     (all equal)
DecoderFactory_SetDecoderFunc-4             16.0 ± 0%      16.0 ± 0%    ~     (all equal)
pkg:github.com/swaggest/rest/response/gzip goos:linux goarch:amd64
Middleware-4                                11.0 ± 0%      11.0 ± 0%    ~     (all equal)
Middleware_control-4                        8.00 ± 0%      8.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-4  1.18µs ± 0%
pkg:github.com/swaggest/rest/request goos:linux goarch:amd64
Decoder_Decode-4                         608ns ± 4%
DecoderFunc_Decode-4                    1.70µs ± 1%
Decoder_Decode_json-4                   18.5µs ± 1%
Decoder_Decode_queryObject-4            3.99µs ± 1%
Decoder_Decode_jsonParam-4              1.56µs ± 0%
DecoderFactory_SetDecoderFunc-4         1.34µs ± 0%
pkg:github.com/swaggest/rest/response/gzip goos:linux goarch:amd64
Middleware-4                            11.3µs ± 1%
Middleware_control-4                    2.71µs ± 0%

name                                    alloc/op
pkg:github.com/swaggest/rest/jsonschema goos:linux goarch:amd64
RequestValidator_ValidateRequestData-4  2.46kB ± 0%
pkg:github.com/swaggest/rest/request goos:linux goarch:amd64
Decoder_Decode-4                          440B ± 0%
DecoderFunc_Decode-4                    1.53kB ± 0%
Decoder_Decode_json-4                   12.4kB ± 0%
Decoder_Decode_queryObject-4            2.01kB ± 0%
Decoder_Decode_jsonParam-4                736B ± 0%
DecoderFactory_SetDecoderFunc-4         1.02kB ± 0%
pkg:github.com/swaggest/rest/response/gzip goos:linux goarch:amd64
Middleware-4                            1.16kB ± 4%
Middleware_control-4                    11.1kB ± 0%

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

Patch:
diff --git a/request/decoder.go b/request/decoder.go
index eb1e316..54c6262 100644
--- a/request/decoder.go
+++ b/request/decoder.go
@@ -79,7 +79,7 @@ func decodeValidate(d *form.Decoder, v interface{}, p url.Values, in rest.ParamI
 func makeDecoder(in rest.ParamIn, formDecoder *form.Decoder, decoderFunc decoderFunc) valueDecoderFunc {
 	return func(r *http.Request, v interface{}, validator rest.Validator) error {
 		ct := r.Header.Get("Content-Type")
-		if in == rest.ParamInFormData && ct != "" && ct != "multipart/form-data" && ct != "application/x-www-form-urlencoded" {
+		if in == rest.ParamInFormData && ct != "" && !strings.HasPrefix(ct, "multipart/form-data") && ct != "application/x-www-form-urlencoded" {
 			return nil
 		}
 


Test patch:
diff --git a/request/file_test.go b/request/file_test.go
index 3387488..77d9d40 100644
--- a/request/file_test.go
+++ b/request/file_test.go
@@ -91,6 +91,9 @@ func TestDecoder_Decode_fileUploadTag(t *testing.T) {
 	u.Interactor = usecase.Interact(func(_ context.Context, input, _ interface{}) error {
 		in, ok := input.(*fileReqTest)
 		assert.True(t, ok)
+
+		assert.Equal(t, "def", in.Simple)
+
 		assert.NotNil(t, in.Upload)
 		assert.NotNil(t, in.UploadHeader)
 		assert.Equal(t, "my.csv", in.UploadHeader.Filename)
@@ -147,6 +150,8 @@ func TestDecoder_Decode_fileUploadTag(t *testing.T) {
 	_, err = writer.Write([]byte(`Hello2!`))
 	assert.NoError(t, err)
 
+	assert.NoError(t, w.WriteField("simple", "def"))
+
 	assert.NoError(t, w.Close())
 
 	hreq := httptest.NewRequest(http.MethodPost, srv.URL+"/receive", &b)