Repository: swaggest/rest
Pull request #65 (2022-03-29 14:46:41)

Add request loader interface for manual decoding, update CI and deps

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

```diff
95c95
< github.com/swaggest/rest/request/decoder.go	Decode	100.0%
---
> github.com/swaggest/rest/request/decoder.go	Decode	91.7%
```
</details>
<!-- Sticky Pull Request Commentunit-test -->
github-actions[bot]: ### Go API Changes

<pre>
# github.com/swaggest/rest/request
## compatible changes
Loader: added

# summary
Inferred base version: v0.2.23
Suggested version: v0.3.0
</pre>
<!-- Sticky Pull Request Commentgorelease -->
codecov[bot]: # [Codecov](https://codecov.io/gh/swaggest/rest/pull/65?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest) Report
> Merging [#65](https://codecov.io/gh/swaggest/rest/pull/65?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest) (25b8fe1) into [master](https://codecov.io/gh/swaggest/rest/commit/a0cbf5592ee4437af255c75766ca07f67c8dbeee?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest) (a0cbf55) will **decrease** coverage by `0.11%`.
> The diff coverage is `0.00%`.

```diff
@@            Coverage Diff             @@
##           master      #65      +/-   ##
==========================================
- Coverage   76.72%   76.60%   -0.12%     
==========================================
  Files          27       27              
  Lines        1319     1321       +2     
==========================================
  Hits         1012     1012              
- Misses        194      195       +1     
- Partials      113      114       +1     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `76.60% <0.00%> (-0.12%)` | :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/65?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/65/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest#diff-cmVxdWVzdC9kZWNvZGVyLmdv) | `67.56% <0.00%> (-3.87%)` | :arrow_down: |

:mega: Codecov can now indicate which changes are the most critical in Pull Requests. [Learn more](https://about.codecov.io/product/feature/runtime-insights/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest)


Patch:
diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml
index 9471f11..0e6bddc 100644
--- a/.github/workflows/bench.yml
+++ b/.github/workflows/bench.yml
@@ -28,7 +28,7 @@ jobs:
     steps:
       - name: Install Go stable
         if: env.GO_VERSION != 'tip'
-        uses: actions/setup-go@v2
+        uses: actions/setup-go@v3
         with:
           go-version: ${{ env.GO_VERSION }}
       - name: Install Go tip
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
index a0f4185..847b042 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -19,12 +19,15 @@ jobs:
     name: golangci-lint
     runs-on: ubuntu-latest
     steps:
+      - uses: actions/setup-go@v3
+        with:
+          go-version: 1.17.x
       - uses: actions/checkout@v2
       - name: golangci-lint
         uses: golangci/golangci-lint-action@v3.1.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.45.0
+          version: v1.45.2
 
           # Optional: working directory, useful for monorepos
           # working-directory: somedir
diff --git a/.github/workflows/gorelease.yml b/.github/workflows/gorelease.yml
index 80fa588..429d5ed 100644
--- a/.github/workflows/gorelease.yml
+++ b/.github/workflows/gorelease.yml
@@ -16,7 +16,7 @@ jobs:
     steps:
       - name: Install Go stable
         if: env.GO_VERSION != 'tip'
-        uses: actions/setup-go@v2
+        uses: actions/setup-go@v3
         with:
           go-version: ${{ env.GO_VERSION }}
       - name: Install Go tip
diff --git a/Makefile b/Makefile
index 7a17304..4d2e364 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-#GOLANGCI_LINT_VERSION := "v1.45.0" # Optional configuration to pinpoint golangci-lint version.
+#GOLANGCI_LINT_VERSION := "v1.45.2" # 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/README.md b/README.md
index dba87ae..9f4a1c1 100644
--- a/README.md
+++ b/README.md
@@ -91,6 +91,9 @@ if [`request.DecoderFactory.ApplyDefaults`](https://pkg.go.dev/github.com/swagge
 set to `true`, fields of request structure that don't have explicit value but have `default` will be populated with 
 default value.
 
+If input structure implements [`request.Loader`](https://pkg.go.dev/github.com/swaggest/rest/request#Loader),  
+then `LoadFromHTTPRequest(r *http.Request) error` method will be invoked to populate input structure instead 
+of automatic decoding. This allows low level control for cases that need it.
 
 ### Response Encoder
 
diff --git a/_examples/go.mod b/_examples/go.mod
index 38edc7a..2604591 100644
--- a/_examples/go.mod
+++ b/_examples/go.mod
@@ -5,14 +5,14 @@ go 1.18
 replace github.com/swaggest/rest => ../
 
 require (
-	github.com/bool64/dev v0.2.8
+	github.com/bool64/dev v0.2.10
 	github.com/bool64/httpmock v0.1.1
 	github.com/bool64/httptestbench v0.1.3
 	github.com/go-chi/chi/v5 v5.0.7
 	github.com/kelseyhightower/envconfig v1.4.0
-	github.com/stretchr/testify v1.7.0
+	github.com/stretchr/testify v1.7.1
 	github.com/swaggest/assertjson v1.6.8
-	github.com/swaggest/jsonschema-go v0.3.25
+	github.com/swaggest/jsonschema-go v0.3.30
 	github.com/swaggest/openapi-go v0.2.15
 	github.com/swaggest/swgui v1.4.4
 	github.com/swaggest/usecase v1.1.2
diff --git a/_examples/go.sum b/_examples/go.sum
index 712ba94..531db44 100644
--- a/_examples/go.sum
+++ b/_examples/go.sum
@@ -5,8 +5,8 @@ github.com/bool64/dev v0.1.35/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2z
 github.com/bool64/dev v0.1.41/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
 github.com/bool64/dev v0.1.42/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
 github.com/bool64/dev v0.2.5/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
-github.com/bool64/dev v0.2.8 h1:6T1hugHixocOGxLF2bus0knc7EOrXTQ7yTTFZ3F4q/U=
-github.com/bool64/dev v0.2.8/go.mod h1:/csLrm+4oDSsKJRIVS0mrywAonLnYKFG8RvGT7Jh9b8=
+github.com/bool64/dev v0.2.10 h1:ypAGBazcwyIy2JvIJio8V3kdqO7AgIAYvcckW54qxr4=
+github.com/bool64/dev v0.2.10/go.mod h1:/csLrm+4oDSsKJRIVS0mrywAonLnYKFG8RvGT7Jh9b8=
 github.com/bool64/httpmock v0.1.1 h1:jpqM0S8efvJfN7Uy5fBUJKu2C640/ZS0yboxpeyVwm0=
 github.com/bool64/httpmock v0.1.1/go.mod h1:Ju7xrs8gVyxANbgIxoxX4Pkj1uHygzPEpGEnfqct+gA=
 github.com/bool64/httptestbench v0.1.3 h1:bPDM3YPtE3o2sqIwjhdTC55wiqwfHsqps2RGcWBesuo=
@@ -75,14 +75,14 @@ github.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0 h1:mj/nMDAwTBiaC
 github.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
 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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/swaggest/assertjson v1.6.8 h1:1O/9UI5M+2OJI7BeEWKGj0wTvpRXZt5FkOJ4nRkY4rA=
 github.com/swaggest/assertjson v1.6.8/go.mod h1:Euf0upn9Vlaf1/llYHTs+Kx5K3vVbpMbsZhth7zlN7M=
 github.com/swaggest/form/v5 v5.0.1 h1:YQH0REX7iMKhtoVPWXREZgbt50VYXNCKK61psnD8Fgo=
 github.com/swaggest/form/v5 v5.0.1/go.mod h1:vdnaSTze7cxVKhWiCabrfm1YeLwWLpb9P941Gxv4FnA=
-github.com/swaggest/jsonschema-go v0.3.25 h1:HH3iaQ9OAgNC1IZiyOys0qXGIdYAzxT2fMSQRzkN3Ys=
-github.com/swaggest/jsonschema-go v0.3.25/go.mod h1:e5wN7j5yYCO8zZ5YL1ekKfcaphBNcr2YbaoceDNWatI=
+github.com/swaggest/jsonschema-go v0.3.30 h1:ZBfKebjPZy7jKU2MvW9N7YmpYldFADr+VCmtgYz83cw=
+github.com/swaggest/jsonschema-go v0.3.30/go.mod h1:pu+U4tVOkJ2hYTY8PlUnNz6+gzFdNyXnEFZtQlELtwc=
 github.com/swaggest/openapi-go v0.2.15 h1:Cp3c0IacJlDEvYYCBzVm3M51bINyI2XCH5RAwulSj/E=
 github.com/swaggest/openapi-go v0.2.15/go.mod h1:3Xd0go1dJkxlSlBHpVV3h76TccououBojV98hhXn6eQ=
 github.com/swaggest/refl v1.0.1 h1:YQHb7Ic6EMpdUpxQmTWmf/O4IWN6iIErxJNWA7LwyyM=
diff --git a/go.mod b/go.mod
index 7f92c55..a9e89dc 100644
--- a/go.mod
+++ b/go.mod
@@ -3,16 +3,16 @@ module github.com/swaggest/rest
 go 1.17
 
 require (
-	github.com/bool64/dev v0.2.8
+	github.com/bool64/dev v0.2.10
 	github.com/bool64/httpmock v0.1.1
 	github.com/bool64/shared v0.1.4
 	github.com/cespare/xxhash/v2 v2.1.2
 	github.com/go-chi/chi/v5 v5.0.7
 	github.com/santhosh-tekuri/jsonschema/v3 v3.1.0
-	github.com/stretchr/testify v1.7.0
+	github.com/stretchr/testify v1.7.1
 	github.com/swaggest/assertjson v1.6.8
 	github.com/swaggest/form/v5 v5.0.1
-	github.com/swaggest/jsonschema-go v0.3.25
+	github.com/swaggest/jsonschema-go v0.3.30
 	github.com/swaggest/openapi-go v0.2.15
 	github.com/swaggest/refl v1.0.1
 	github.com/swaggest/usecase v1.1.2
diff --git a/go.sum b/go.sum
index bcad0dc..056a894 100644
--- a/go.sum
+++ b/go.sum
@@ -4,8 +4,8 @@ github.com/bool64/dev v0.1.35/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2z
 github.com/bool64/dev v0.1.41/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
 github.com/bool64/dev v0.1.42/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
 github.com/bool64/dev v0.2.5/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
-github.com/bool64/dev v0.2.8 h1:6T1hugHixocOGxLF2bus0knc7EOrXTQ7yTTFZ3F4q/U=
-github.com/bool64/dev v0.2.8/go.mod h1:/csLrm+4oDSsKJRIVS0mrywAonLnYKFG8RvGT7Jh9b8=
+github.com/bool64/dev v0.2.10 h1:ypAGBazcwyIy2JvIJio8V3kdqO7AgIAYvcckW54qxr4=
+github.com/bool64/dev v0.2.10/go.mod h1:/csLrm+4oDSsKJRIVS0mrywAonLnYKFG8RvGT7Jh9b8=
 github.com/bool64/httpmock v0.1.1 h1:jpqM0S8efvJfN7Uy5fBUJKu2C640/ZS0yboxpeyVwm0=
 github.com/bool64/httpmock v0.1.1/go.mod h1:Ju7xrs8gVyxANbgIxoxX4Pkj1uHygzPEpGEnfqct+gA=
 github.com/bool64/shared v0.1.3/go.mod h1:RF1p1Oi29ofgOvinBpetbF5mceOUP3kpMkvLbWOmtm0=
@@ -65,15 +65,16 @@ 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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/swaggest/assertjson v1.6.8 h1:1O/9UI5M+2OJI7BeEWKGj0wTvpRXZt5FkOJ4nRkY4rA=
 github.com/swaggest/assertjson v1.6.8/go.mod h1:Euf0upn9Vlaf1/llYHTs+Kx5K3vVbpMbsZhth7zlN7M=
 github.com/swaggest/form/v5 v5.0.1 h1:YQH0REX7iMKhtoVPWXREZgbt50VYXNCKK61psnD8Fgo=
 github.com/swaggest/form/v5 v5.0.1/go.mod h1:vdnaSTze7cxVKhWiCabrfm1YeLwWLpb9P941Gxv4FnA=
 github.com/swaggest/jsonschema-go v0.3.24/go.mod h1:B2ZSqrrlkj21zhywlkh8VnKBmuqUwDv3dLQoPjgHk7M=
-github.com/swaggest/jsonschema-go v0.3.25 h1:HH3iaQ9OAgNC1IZiyOys0qXGIdYAzxT2fMSQRzkN3Ys=
-github.com/swaggest/jsonschema-go v0.3.25/go.mod h1:e5wN7j5yYCO8zZ5YL1ekKfcaphBNcr2YbaoceDNWatI=
+github.com/swaggest/jsonschema-go v0.3.30 h1:ZBfKebjPZy7jKU2MvW9N7YmpYldFADr+VCmtgYz83cw=
+github.com/swaggest/jsonschema-go v0.3.30/go.mod h1:pu+U4tVOkJ2hYTY8PlUnNz6+gzFdNyXnEFZtQlELtwc=
 github.com/swaggest/openapi-go v0.2.15 h1:Cp3c0IacJlDEvYYCBzVm3M51bINyI2XCH5RAwulSj/E=
 github.com/swaggest/openapi-go v0.2.15/go.mod h1:3Xd0go1dJkxlSlBHpVV3h76TccououBojV98hhXn6eQ=
 github.com/swaggest/refl v1.0.0/go.mod h1:acYd5x8NNxivp+ZHdRZKJYz66n/qjo3Q9Sa/jAivljQ=
diff --git a/request/decoder.go b/request/decoder.go
index d3f1c1a..27b92d8 100644
--- a/request/decoder.go
+++ b/request/decoder.go
@@ -11,6 +11,13 @@ import (
 )
 
 type (
+	// Loader loads data from http.Request.
+	//
+	// Implement this interface on a pointer to your input structure to disable automatic request mapping.
+	Loader interface {
+		LoadFromHTTPRequest(r *http.Request) error
+	}
+
 	decoderFunc      func(r *http.Request) (url.Values, error)
 	valueDecoderFunc func(r *http.Request, v interface{}, validator rest.Validator) error
 )
@@ -51,6 +58,10 @@ var _ nethttp.RequestDecoder = &decoder{}
 
 // Decode populates and validates input with data from http request.
 func (d *decoder) Decode(r *http.Request, input interface{}, validator rest.Validator) error {
+	if i, ok := input.(Loader); ok {
+		return i.LoadFromHTTPRequest(r)
+	}
+
 	for i, decode := range d.decoders {
 		err := decode(r, input, validator)
 		if err != nil {


Test patch:
diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml
index 97ff2c0..6b6b717 100644
--- a/.github/workflows/test-unit.yml
+++ b/.github/workflows/test-unit.yml
@@ -25,7 +25,7 @@ jobs:
     steps:
       - name: Install Go stable
         if: matrix.go-version != 'tip'
-        uses: actions/setup-go@v2
+        uses: actions/setup-go@v3
         with:
           go-version: ${{ matrix.go-version }}
       - name: Install Go tip
diff --git a/request/decoder_test.go b/request/decoder_test.go
index 1133d50..736f7b0 100644
--- a/request/decoder_test.go
+++ b/request/decoder_test.go
@@ -406,3 +406,40 @@ func TestDecoder_Decode_dateTime(t *testing.T) {
 	err = dec.Decode(req, input, validator)
 	assert.NoError(t, err, fmt.Sprintf("%v", err))
 }
+
+type inputWithLoader struct {
+	Time time.Time    `query:"time"`
+	Date jschema.Date `query:"date"`
+
+	load func(r *http.Request) error
+}
+
+func (i *inputWithLoader) LoadFromHTTPRequest(r *http.Request) error {
+	return i.load(r)
+}
+
+func TestDecoder_Decode_manualLoader(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 := new(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())
+}