Repository: zeromicro/go-zero
Pull request #3041 (2023-03-18 12:07:41)

feat: rest validation on http requests

codecov[bot]: ## [Codecov](https://codecov.io/gh/zeromicro/go-zero/pull/3041?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#3041](https://codecov.io/gh/zeromicro/go-zero/pull/3041?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (a17874e) into [master](https://codecov.io/gh/zeromicro/go-zero/commit/7a647ca40c6392e90328a168c0a33ce0694f359d?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (7a647ca) will **increase** coverage by `0.01%`.
> The diff coverage is `100.00%`.


| [Impacted Files](https://codecov.io/gh/zeromicro/go-zero/pull/3041?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [core/executors/periodicalexecutor.go](https://codecov.io/gh/zeromicro/go-zero/pull/3041?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-Y29yZS9leGVjdXRvcnMvcGVyaW9kaWNhbGV4ZWN1dG9yLmdv) | `100.00% <ø> (ø)` | |
| [rest/httpx/requests.go](https://codecov.io/gh/zeromicro/go-zero/pull/3041?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cmVzdC9odHRweC9yZXF1ZXN0cy5nbw==) | `100.00% <100.00%> (ø)` | |

... and [1 file with indirect coverage changes](https://codecov.io/gh/zeromicro/go-zero/pull/3041/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)


Patch:
diff --git a/core/executors/periodicalexecutor.go b/core/executors/periodicalexecutor.go
index 6722a657..e13f907e 100644
--- a/core/executors/periodicalexecutor.go
+++ b/core/executors/periodicalexecutor.go
@@ -81,7 +81,7 @@ func (pe *PeriodicalExecutor) Flush() bool {
 	}())
 }
 
-// Sync lets caller to run fn thread-safe with pe, especially for the underlying container.
+// Sync lets caller run fn thread-safe with pe, especially for the underlying container.
 func (pe *PeriodicalExecutor) Sync(fn func()) {
 	pe.lock.Lock()
 	defer pe.lock.Unlock()
diff --git a/core/validation/validator.go b/core/validation/validator.go
new file mode 100644
index 00000000..2903ac9c
--- /dev/null
+++ b/core/validation/validator.go
@@ -0,0 +1,7 @@
+package validation
+
+// Validator represents a validator.
+type Validator interface {
+	// Validate validates the value.
+	Validate() error
+}
diff --git a/rest/httpx/requests.go b/rest/httpx/requests.go
index cd088bb0..02d29eca 100644
--- a/rest/httpx/requests.go
+++ b/rest/httpx/requests.go
@@ -7,6 +7,7 @@ import (
 	"sync/atomic"
 
 	"github.com/zeromicro/go-zero/core/mapping"
+	"github.com/zeromicro/go-zero/core/validation"
 	"github.com/zeromicro/go-zero/rest/internal/encoding"
 	"github.com/zeromicro/go-zero/rest/internal/header"
 	"github.com/zeromicro/go-zero/rest/pathvar"
@@ -51,7 +52,9 @@ func Parse(r *http.Request, v any) error {
 		return err
 	}
 
-	if val := validator.Load(); val != nil {
+	if valid, ok := v.(validation.Validator); ok {
+		return valid.Validate()
+	} else if val := validator.Load(); val != nil {
 		return val.(Validator).Validate(r, v)
 	}
 


Test patch:
diff --git a/rest/httpx/requests_test.go b/rest/httpx/requests_test.go
index fa7dd9dc..aa462176 100644
--- a/rest/httpx/requests_test.go
+++ b/rest/httpx/requests_test.go
@@ -354,6 +354,14 @@ func TestParseWithValidatorWithError(t *testing.T) {
 	assert.Error(t, Parse(r, &v))
 }
 
+func TestParseWithValidatorRequest(t *testing.T) {
+	SetValidator(mockValidator{})
+	var v mockRequest
+	r, err := http.NewRequest(http.MethodGet, "/a?&age=18", http.NoBody)
+	assert.Nil(t, err)
+	assert.Error(t, Parse(r, &v))
+}
+
 func BenchmarkParseRaw(b *testing.B) {
 	r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello&age=18&percent=3.4", http.NoBody)
 	if err != nil {
@@ -410,3 +418,15 @@ func (m mockValidator) Validate(r *http.Request, data any) error {
 
 	return nil
 }
+
+type mockRequest struct {
+	Name string `json:"name,optional"`
+}
+
+func (m mockRequest) Validate() error {
+	if m.Name != "hello" {
+		return errors.New("name is not hello")
+	}
+
+	return nil
+}