From 4f789e779f5ecb6483d6ad246026996621c483d7 Mon Sep 17 00:00:00 2001 From: spectatorMrZ Date: Tue, 1 Aug 2023 23:47:40 +0800 Subject: [PATCH 1/6] feat: add custom unset err when parse request --- core/mapping/unmarshaler.go | 20 +++++++++++++++++--- core/mapping/unmarshaler_test.go | 19 +++++++++++++++++++ rest/httpx/requests.go | 21 ++++++++++++++++----- rest/httpx/requests_test.go | 24 ++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 8 deletions(-) diff --git a/core/mapping/unmarshaler.go b/core/mapping/unmarshaler.go index b6281e42e75a..505e8ab4d2cb 100644 --- a/core/mapping/unmarshaler.go +++ b/core/mapping/unmarshaler.go @@ -47,9 +47,10 @@ type ( UnmarshalOption func(*unmarshalOptions) unmarshalOptions struct { - fillDefault bool - fromString bool - canonicalKey func(key string) string + fillDefault bool + fromString bool + canonicalKey func(key string) string + customFieldUnsetErr func(key string) error } ) @@ -860,6 +861,9 @@ func (u *Unmarshaler) processNamedFieldWithoutValue(fieldType reflect.Type, valu } if required { + if u.opts.customFieldUnsetErr != nil { + return u.opts.customFieldUnsetErr(fullName) + } return fmt.Errorf("%q is not set", fullName) } @@ -869,6 +873,9 @@ func (u *Unmarshaler) processNamedFieldWithoutValue(fieldType reflect.Type, valu } default: if !opts.optional() { + if u.opts.customFieldUnsetErr != nil { + return u.opts.customFieldUnsetErr(fullName) + } return newInitError(fullName) } } @@ -921,6 +928,13 @@ func WithCanonicalKeyFunc(f func(string) string) UnmarshalOption { } } +// WithCustomFieldUnsetErr customizes an Unmarshaler with custom field unset error. +func WithCustomFieldUnsetErr(f func(string) error) UnmarshalOption { + return func(opt *unmarshalOptions) { + opt.customFieldUnsetErr = f + } +} + // WithDefault customizes an Unmarshaler with fill default values. func WithDefault() UnmarshalOption { return func(opt *unmarshalOptions) { diff --git a/core/mapping/unmarshaler_test.go b/core/mapping/unmarshaler_test.go index c65e69ffae80..16d6941875d5 100644 --- a/core/mapping/unmarshaler_test.go +++ b/core/mapping/unmarshaler_test.go @@ -4789,6 +4789,25 @@ func TestUnmarshalJsonBytesWithAnonymousFieldNotInOptions(t *testing.T) { assert.Error(t, UnmarshalJsonBytes(input, &c)) } +func TestUnmarshalJsonBytesWithCustomFieldUnsetErr(t *testing.T) { + type ( + Conf struct { + Name string `json:"name"` + } + ) + + var ( + input = []byte(`{}`) + c Conf + ) + + e := UnmarshalJsonBytes(input, &c, WithCustomFieldUnsetErr(func(field string) error { + // Here you can customize exceptions and internationalization processing + return fmt.Errorf("the key %s is unset", field) + })) + assert.Error(t, e) +} + func TestUnmarshalNestedPtr(t *testing.T) { type inner struct { Int **int `key:"int"` diff --git a/rest/httpx/requests.go b/rest/httpx/requests.go index 02d29ecab256..732f39132492 100644 --- a/rest/httpx/requests.go +++ b/rest/httpx/requests.go @@ -23,9 +23,10 @@ const ( ) var ( - formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues()) - pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues()) - validator atomic.Value + formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues()) + pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues()) + validator atomic.Value + customFieldUnsetErr func(key string) error ) // Validator defines the interface for validating the request. @@ -100,12 +101,16 @@ func ParseHeader(headerValue string) map[string]string { // ParseJsonBody parses the post request which contains json in body. func ParseJsonBody(r *http.Request, v any) error { + var opts []mapping.UnmarshalOption + if customFieldUnsetErr != nil { + opts = append(opts, mapping.WithCustomFieldUnsetErr(customFieldUnsetErr)) + } if withJsonBody(r) { reader := io.LimitReader(r.Body, maxBodyLen) - return mapping.UnmarshalJsonReader(reader, v) + return mapping.UnmarshalJsonReader(reader, v, opts...) } - return mapping.UnmarshalJsonMap(nil, v) + return mapping.UnmarshalJsonMap(nil, v, opts...) } // ParsePath parses the symbols reside in url path. @@ -130,3 +135,9 @@ func SetValidator(val Validator) { func withJsonBody(r *http.Request) bool { return r.ContentLength > 0 && strings.Contains(r.Header.Get(header.ContentType), header.ApplicationJson) } + +func SetCustomUnsetError(f func(string) error) { + customFieldUnsetErr = f + formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues(), mapping.WithCustomFieldUnsetErr(f)) + pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues(), mapping.WithCustomFieldUnsetErr(f)) +} diff --git a/rest/httpx/requests_test.go b/rest/httpx/requests_test.go index aa462176ac59..eff6504e82be 100644 --- a/rest/httpx/requests_test.go +++ b/rest/httpx/requests_test.go @@ -2,6 +2,7 @@ package httpx import ( "errors" + "fmt" "net/http" "net/http/httptest" "reflect" @@ -257,6 +258,29 @@ func TestParseJsonBody(t *testing.T) { }) } +func TestParseCustomUnsetErr(t *testing.T) { + SetCustomUnsetError(func(tag string) error { + return fmt.Errorf("custom %s unset error", tag) + }) + v := struct { + Name string `form:"name"` + Percent float64 `form:"percent"` + }{} + + gr, err := http.NewRequest(http.MethodGet, "/a?name=hello", http.NoBody) + assert.Nil(t, err) + assert.EqualErrorf(t, Parse(gr, &v), "custom percent unset error", "custom unset error") + + pv := struct { + Name string `json:"name"` + Percent float64 `json:"percent"` + }{} + body := `{"name":"hello"}` + pr := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body)) + pr.Header.Set(ContentType, header.JsonContentType) + assert.EqualErrorf(t, Parse(pr, &pv), "custom percent unset error", "custom unset error") +} + func TestParseRequired(t *testing.T) { v := struct { Name string `form:"name"` From 0f838eb3b07106cd26267d62da9dda5f79c8b62d Mon Sep 17 00:00:00 2001 From: spectatorMrZ Date: Wed, 2 Aug 2023 03:59:19 +0800 Subject: [PATCH 2/6] feat: custom unset error add context --- core/mapping/unmarshaler.go | 13 ++++++++++- rest/httpx/requests.go | 33 ++++++++++++++++++---------- rest/httpx/requests_test.go | 44 +++++++++++++++++++++++-------------- 3 files changed, 60 insertions(+), 30 deletions(-) diff --git a/core/mapping/unmarshaler.go b/core/mapping/unmarshaler.go index 505e8ab4d2cb..2ad9540bb1fd 100644 --- a/core/mapping/unmarshaler.go +++ b/core/mapping/unmarshaler.go @@ -67,6 +67,17 @@ func NewUnmarshaler(key string, opts ...UnmarshalOption) *Unmarshaler { return &unmarshaler } +func WithOpts(u *Unmarshaler, opts ...UnmarshalOption) *Unmarshaler { + if u == nil { + return u + } + for _, opt := range opts { + opt(&u.opts) + } + + return u +} + // UnmarshalKey unmarshals m into v with tag key. func UnmarshalKey(m map[string]any, v any) error { return keyUnmarshaler.Unmarshal(m, v) @@ -929,7 +940,7 @@ func WithCanonicalKeyFunc(f func(string) string) UnmarshalOption { } // WithCustomFieldUnsetErr customizes an Unmarshaler with custom field unset error. -func WithCustomFieldUnsetErr(f func(string) error) UnmarshalOption { +func WithCustomFieldUnsetErr(f func(fullName string) error) UnmarshalOption { return func(opt *unmarshalOptions) { opt.customFieldUnsetErr = f } diff --git a/rest/httpx/requests.go b/rest/httpx/requests.go index 732f39132492..f66edf16384b 100644 --- a/rest/httpx/requests.go +++ b/rest/httpx/requests.go @@ -1,6 +1,7 @@ package httpx import ( + "context" "io" "net/http" "strings" @@ -26,7 +27,7 @@ var ( formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues()) pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues()) validator atomic.Value - customFieldUnsetErr func(key string) error + customFieldUnsetErr func(ctx context.Context, key string) error ) // Validator defines the interface for validating the request. @@ -73,8 +74,8 @@ func ParseForm(r *http.Request, v any) error { if err != nil { return err } - - return formUnmarshaler.Unmarshal(params, v) + unmarshaler := mapping.WithOpts(formUnmarshaler, getUnmarshalOptions(r)...) + return unmarshaler.Unmarshal(params, v) } // ParseHeader parses the request header and returns a map. @@ -101,10 +102,7 @@ func ParseHeader(headerValue string) map[string]string { // ParseJsonBody parses the post request which contains json in body. func ParseJsonBody(r *http.Request, v any) error { - var opts []mapping.UnmarshalOption - if customFieldUnsetErr != nil { - opts = append(opts, mapping.WithCustomFieldUnsetErr(customFieldUnsetErr)) - } + opts := getUnmarshalOptions(r) if withJsonBody(r) { reader := io.LimitReader(r.Body, maxBodyLen) return mapping.UnmarshalJsonReader(reader, v, opts...) @@ -121,8 +119,8 @@ func ParsePath(r *http.Request, v any) error { for k, v := range vars { m[k] = v } - - return pathUnmarshaler.Unmarshal(m, v) + unmarshaler := mapping.WithOpts(pathUnmarshaler, getUnmarshalOptions(r)...) + return unmarshaler.Unmarshal(m, v) } // SetValidator sets the validator. @@ -136,8 +134,19 @@ func withJsonBody(r *http.Request) bool { return r.ContentLength > 0 && strings.Contains(r.Header.Get(header.ContentType), header.ApplicationJson) } -func SetCustomUnsetError(f func(string) error) { +func getUnmarshalOptions(r *http.Request) []mapping.UnmarshalOption { + var opts []mapping.UnmarshalOption + if customFieldUnsetErr != nil { + unsetErrFun := func(key string) error { + return customFieldUnsetErr(r.Context(), key) + } + opts = append(opts, mapping.WithCustomFieldUnsetErr(unsetErrFun)) + } + return opts +} + +func SetCustomUnsetError(f func(ctx context.Context, fullName string) error) { customFieldUnsetErr = f - formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues(), mapping.WithCustomFieldUnsetErr(f)) - pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues(), mapping.WithCustomFieldUnsetErr(f)) + //formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues(), mapping.WithCustomFieldUnsetErr(f)) + //pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues(), mapping.WithCustomFieldUnsetErr(f)) } diff --git a/rest/httpx/requests_test.go b/rest/httpx/requests_test.go index eff6504e82be..c92f9083c7d7 100644 --- a/rest/httpx/requests_test.go +++ b/rest/httpx/requests_test.go @@ -1,6 +1,7 @@ package httpx import ( + "context" "errors" "fmt" "net/http" @@ -259,26 +260,35 @@ func TestParseJsonBody(t *testing.T) { } func TestParseCustomUnsetErr(t *testing.T) { - SetCustomUnsetError(func(tag string) error { - return fmt.Errorf("custom %s unset error", tag) + startCtx := context.Background() + ctxKey := "method" + + SetCustomUnsetError(func(ctx context.Context, tag string) error { + return fmt.Errorf("%s: custom %s unset error", ctx.Value(ctxKey).(string), tag) }) - v := struct { - Name string `form:"name"` - Percent float64 `form:"percent"` - }{} + t.Run("request get", func(t *testing.T) { + v := struct { + Name string `form:"name"` + Percent float64 `form:"percent"` + }{} - gr, err := http.NewRequest(http.MethodGet, "/a?name=hello", http.NoBody) - assert.Nil(t, err) - assert.EqualErrorf(t, Parse(gr, &v), "custom percent unset error", "custom unset error") + gr, err := http.NewRequest(http.MethodGet, "/a?name=hello", http.NoBody) + gr = gr.WithContext(context.WithValue(startCtx, ctxKey, "GET")) + assert.Nil(t, err) + assert.EqualErrorf(t, Parse(gr, &v), "GET: custom percent unset error", "custom unset error") + }) - pv := struct { - Name string `json:"name"` - Percent float64 `json:"percent"` - }{} - body := `{"name":"hello"}` - pr := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body)) - pr.Header.Set(ContentType, header.JsonContentType) - assert.EqualErrorf(t, Parse(pr, &pv), "custom percent unset error", "custom unset error") + t.Run("request post", func(t *testing.T) { + pv := struct { + Name string `json:"name"` + Percent float64 `json:"percent"` + }{} + body := `{"name":"hello"}` + pr := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body)) + pr.Header.Set(ContentType, header.JsonContentType) + pr = pr.WithContext(context.WithValue(startCtx, ctxKey, "POST")) + assert.EqualErrorf(t, Parse(pr, &pv), "POST: custom percent unset error", "custom unset error") + }) } func TestParseRequired(t *testing.T) { From 269bcfb70a8e7093b11fd763fbf7513c3b278169 Mon Sep 17 00:00:00 2001 From: spectatorMrZ Date: Wed, 2 Aug 2023 05:21:15 +0800 Subject: [PATCH 3/6] test: add unmarshal test with CustomFieldUnsetErr --- core/mapping/unmarshaler_test.go | 38 +++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/core/mapping/unmarshaler_test.go b/core/mapping/unmarshaler_test.go index 16d6941875d5..aaf29979a5c7 100644 --- a/core/mapping/unmarshaler_test.go +++ b/core/mapping/unmarshaler_test.go @@ -4791,21 +4791,39 @@ func TestUnmarshalJsonBytesWithAnonymousFieldNotInOptions(t *testing.T) { func TestUnmarshalJsonBytesWithCustomFieldUnsetErr(t *testing.T) { type ( - Conf struct { + InnerConf struct { Name string `json:"name"` } + Conf struct { + Name string `json:"name"` + Inner InnerConf `json:"inner"` + } ) - var ( - input = []byte(`{}`) - c Conf - ) + t.Run("inner name unset", func(t *testing.T) { + var ( + input = []byte(`{"inner": {}, "name": "world"}`) + c Conf + ) - e := UnmarshalJsonBytes(input, &c, WithCustomFieldUnsetErr(func(field string) error { - // Here you can customize exceptions and internationalization processing - return fmt.Errorf("the key %s is unset", field) - })) - assert.Error(t, e) + e := UnmarshalJsonBytes(input, &c, WithCustomFieldUnsetErr(func(field string) error { + // Here you can customize exceptions and internationalization processing + return fmt.Errorf("the key %s is unset", field) + })) + assert.Error(t, e) + }) + + t.Run("name unset", func(t *testing.T) { + var ( + input = []byte(`{"inner": {"name":"hello"}`) + c Conf + ) + e := UnmarshalJsonBytes(input, &c, WithCustomFieldUnsetErr(func(field string) error { + // Here you can customize exceptions and internationalization processing + return fmt.Errorf("the key %s is unset", field) + })) + assert.Error(t, e) + }) } func TestUnmarshalNestedPtr(t *testing.T) { From 7a07ae8460ab40952556c7087b8db8792036d455 Mon Sep 17 00:00:00 2001 From: spectatorMrZ Date: Tue, 1 Aug 2023 23:47:40 +0800 Subject: [PATCH 4/6] feat: add custom unset err when parse request --- core/mapping/unmarshaler.go | 20 +++++++++++++++++--- core/mapping/unmarshaler_test.go | 19 +++++++++++++++++++ rest/httpx/requests.go | 21 ++++++++++++++++----- rest/httpx/requests_test.go | 24 ++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 8 deletions(-) diff --git a/core/mapping/unmarshaler.go b/core/mapping/unmarshaler.go index b6281e42e75a..505e8ab4d2cb 100644 --- a/core/mapping/unmarshaler.go +++ b/core/mapping/unmarshaler.go @@ -47,9 +47,10 @@ type ( UnmarshalOption func(*unmarshalOptions) unmarshalOptions struct { - fillDefault bool - fromString bool - canonicalKey func(key string) string + fillDefault bool + fromString bool + canonicalKey func(key string) string + customFieldUnsetErr func(key string) error } ) @@ -860,6 +861,9 @@ func (u *Unmarshaler) processNamedFieldWithoutValue(fieldType reflect.Type, valu } if required { + if u.opts.customFieldUnsetErr != nil { + return u.opts.customFieldUnsetErr(fullName) + } return fmt.Errorf("%q is not set", fullName) } @@ -869,6 +873,9 @@ func (u *Unmarshaler) processNamedFieldWithoutValue(fieldType reflect.Type, valu } default: if !opts.optional() { + if u.opts.customFieldUnsetErr != nil { + return u.opts.customFieldUnsetErr(fullName) + } return newInitError(fullName) } } @@ -921,6 +928,13 @@ func WithCanonicalKeyFunc(f func(string) string) UnmarshalOption { } } +// WithCustomFieldUnsetErr customizes an Unmarshaler with custom field unset error. +func WithCustomFieldUnsetErr(f func(string) error) UnmarshalOption { + return func(opt *unmarshalOptions) { + opt.customFieldUnsetErr = f + } +} + // WithDefault customizes an Unmarshaler with fill default values. func WithDefault() UnmarshalOption { return func(opt *unmarshalOptions) { diff --git a/core/mapping/unmarshaler_test.go b/core/mapping/unmarshaler_test.go index c65e69ffae80..16d6941875d5 100644 --- a/core/mapping/unmarshaler_test.go +++ b/core/mapping/unmarshaler_test.go @@ -4789,6 +4789,25 @@ func TestUnmarshalJsonBytesWithAnonymousFieldNotInOptions(t *testing.T) { assert.Error(t, UnmarshalJsonBytes(input, &c)) } +func TestUnmarshalJsonBytesWithCustomFieldUnsetErr(t *testing.T) { + type ( + Conf struct { + Name string `json:"name"` + } + ) + + var ( + input = []byte(`{}`) + c Conf + ) + + e := UnmarshalJsonBytes(input, &c, WithCustomFieldUnsetErr(func(field string) error { + // Here you can customize exceptions and internationalization processing + return fmt.Errorf("the key %s is unset", field) + })) + assert.Error(t, e) +} + func TestUnmarshalNestedPtr(t *testing.T) { type inner struct { Int **int `key:"int"` diff --git a/rest/httpx/requests.go b/rest/httpx/requests.go index 02d29ecab256..732f39132492 100644 --- a/rest/httpx/requests.go +++ b/rest/httpx/requests.go @@ -23,9 +23,10 @@ const ( ) var ( - formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues()) - pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues()) - validator atomic.Value + formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues()) + pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues()) + validator atomic.Value + customFieldUnsetErr func(key string) error ) // Validator defines the interface for validating the request. @@ -100,12 +101,16 @@ func ParseHeader(headerValue string) map[string]string { // ParseJsonBody parses the post request which contains json in body. func ParseJsonBody(r *http.Request, v any) error { + var opts []mapping.UnmarshalOption + if customFieldUnsetErr != nil { + opts = append(opts, mapping.WithCustomFieldUnsetErr(customFieldUnsetErr)) + } if withJsonBody(r) { reader := io.LimitReader(r.Body, maxBodyLen) - return mapping.UnmarshalJsonReader(reader, v) + return mapping.UnmarshalJsonReader(reader, v, opts...) } - return mapping.UnmarshalJsonMap(nil, v) + return mapping.UnmarshalJsonMap(nil, v, opts...) } // ParsePath parses the symbols reside in url path. @@ -130,3 +135,9 @@ func SetValidator(val Validator) { func withJsonBody(r *http.Request) bool { return r.ContentLength > 0 && strings.Contains(r.Header.Get(header.ContentType), header.ApplicationJson) } + +func SetCustomUnsetError(f func(string) error) { + customFieldUnsetErr = f + formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues(), mapping.WithCustomFieldUnsetErr(f)) + pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues(), mapping.WithCustomFieldUnsetErr(f)) +} diff --git a/rest/httpx/requests_test.go b/rest/httpx/requests_test.go index aa462176ac59..eff6504e82be 100644 --- a/rest/httpx/requests_test.go +++ b/rest/httpx/requests_test.go @@ -2,6 +2,7 @@ package httpx import ( "errors" + "fmt" "net/http" "net/http/httptest" "reflect" @@ -257,6 +258,29 @@ func TestParseJsonBody(t *testing.T) { }) } +func TestParseCustomUnsetErr(t *testing.T) { + SetCustomUnsetError(func(tag string) error { + return fmt.Errorf("custom %s unset error", tag) + }) + v := struct { + Name string `form:"name"` + Percent float64 `form:"percent"` + }{} + + gr, err := http.NewRequest(http.MethodGet, "/a?name=hello", http.NoBody) + assert.Nil(t, err) + assert.EqualErrorf(t, Parse(gr, &v), "custom percent unset error", "custom unset error") + + pv := struct { + Name string `json:"name"` + Percent float64 `json:"percent"` + }{} + body := `{"name":"hello"}` + pr := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body)) + pr.Header.Set(ContentType, header.JsonContentType) + assert.EqualErrorf(t, Parse(pr, &pv), "custom percent unset error", "custom unset error") +} + func TestParseRequired(t *testing.T) { v := struct { Name string `form:"name"` From 487de975a762ee2ab9df86b8c5d245527a6f9ddf Mon Sep 17 00:00:00 2001 From: spectatorMrZ Date: Wed, 2 Aug 2023 03:59:19 +0800 Subject: [PATCH 5/6] feat: custom unset error add context --- core/mapping/unmarshaler.go | 13 ++++++++++- rest/httpx/requests.go | 33 ++++++++++++++++++---------- rest/httpx/requests_test.go | 44 +++++++++++++++++++++++-------------- 3 files changed, 60 insertions(+), 30 deletions(-) diff --git a/core/mapping/unmarshaler.go b/core/mapping/unmarshaler.go index 505e8ab4d2cb..2ad9540bb1fd 100644 --- a/core/mapping/unmarshaler.go +++ b/core/mapping/unmarshaler.go @@ -67,6 +67,17 @@ func NewUnmarshaler(key string, opts ...UnmarshalOption) *Unmarshaler { return &unmarshaler } +func WithOpts(u *Unmarshaler, opts ...UnmarshalOption) *Unmarshaler { + if u == nil { + return u + } + for _, opt := range opts { + opt(&u.opts) + } + + return u +} + // UnmarshalKey unmarshals m into v with tag key. func UnmarshalKey(m map[string]any, v any) error { return keyUnmarshaler.Unmarshal(m, v) @@ -929,7 +940,7 @@ func WithCanonicalKeyFunc(f func(string) string) UnmarshalOption { } // WithCustomFieldUnsetErr customizes an Unmarshaler with custom field unset error. -func WithCustomFieldUnsetErr(f func(string) error) UnmarshalOption { +func WithCustomFieldUnsetErr(f func(fullName string) error) UnmarshalOption { return func(opt *unmarshalOptions) { opt.customFieldUnsetErr = f } diff --git a/rest/httpx/requests.go b/rest/httpx/requests.go index 732f39132492..f66edf16384b 100644 --- a/rest/httpx/requests.go +++ b/rest/httpx/requests.go @@ -1,6 +1,7 @@ package httpx import ( + "context" "io" "net/http" "strings" @@ -26,7 +27,7 @@ var ( formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues()) pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues()) validator atomic.Value - customFieldUnsetErr func(key string) error + customFieldUnsetErr func(ctx context.Context, key string) error ) // Validator defines the interface for validating the request. @@ -73,8 +74,8 @@ func ParseForm(r *http.Request, v any) error { if err != nil { return err } - - return formUnmarshaler.Unmarshal(params, v) + unmarshaler := mapping.WithOpts(formUnmarshaler, getUnmarshalOptions(r)...) + return unmarshaler.Unmarshal(params, v) } // ParseHeader parses the request header and returns a map. @@ -101,10 +102,7 @@ func ParseHeader(headerValue string) map[string]string { // ParseJsonBody parses the post request which contains json in body. func ParseJsonBody(r *http.Request, v any) error { - var opts []mapping.UnmarshalOption - if customFieldUnsetErr != nil { - opts = append(opts, mapping.WithCustomFieldUnsetErr(customFieldUnsetErr)) - } + opts := getUnmarshalOptions(r) if withJsonBody(r) { reader := io.LimitReader(r.Body, maxBodyLen) return mapping.UnmarshalJsonReader(reader, v, opts...) @@ -121,8 +119,8 @@ func ParsePath(r *http.Request, v any) error { for k, v := range vars { m[k] = v } - - return pathUnmarshaler.Unmarshal(m, v) + unmarshaler := mapping.WithOpts(pathUnmarshaler, getUnmarshalOptions(r)...) + return unmarshaler.Unmarshal(m, v) } // SetValidator sets the validator. @@ -136,8 +134,19 @@ func withJsonBody(r *http.Request) bool { return r.ContentLength > 0 && strings.Contains(r.Header.Get(header.ContentType), header.ApplicationJson) } -func SetCustomUnsetError(f func(string) error) { +func getUnmarshalOptions(r *http.Request) []mapping.UnmarshalOption { + var opts []mapping.UnmarshalOption + if customFieldUnsetErr != nil { + unsetErrFun := func(key string) error { + return customFieldUnsetErr(r.Context(), key) + } + opts = append(opts, mapping.WithCustomFieldUnsetErr(unsetErrFun)) + } + return opts +} + +func SetCustomUnsetError(f func(ctx context.Context, fullName string) error) { customFieldUnsetErr = f - formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues(), mapping.WithCustomFieldUnsetErr(f)) - pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues(), mapping.WithCustomFieldUnsetErr(f)) + //formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues(), mapping.WithCustomFieldUnsetErr(f)) + //pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues(), mapping.WithCustomFieldUnsetErr(f)) } diff --git a/rest/httpx/requests_test.go b/rest/httpx/requests_test.go index eff6504e82be..c92f9083c7d7 100644 --- a/rest/httpx/requests_test.go +++ b/rest/httpx/requests_test.go @@ -1,6 +1,7 @@ package httpx import ( + "context" "errors" "fmt" "net/http" @@ -259,26 +260,35 @@ func TestParseJsonBody(t *testing.T) { } func TestParseCustomUnsetErr(t *testing.T) { - SetCustomUnsetError(func(tag string) error { - return fmt.Errorf("custom %s unset error", tag) + startCtx := context.Background() + ctxKey := "method" + + SetCustomUnsetError(func(ctx context.Context, tag string) error { + return fmt.Errorf("%s: custom %s unset error", ctx.Value(ctxKey).(string), tag) }) - v := struct { - Name string `form:"name"` - Percent float64 `form:"percent"` - }{} + t.Run("request get", func(t *testing.T) { + v := struct { + Name string `form:"name"` + Percent float64 `form:"percent"` + }{} - gr, err := http.NewRequest(http.MethodGet, "/a?name=hello", http.NoBody) - assert.Nil(t, err) - assert.EqualErrorf(t, Parse(gr, &v), "custom percent unset error", "custom unset error") + gr, err := http.NewRequest(http.MethodGet, "/a?name=hello", http.NoBody) + gr = gr.WithContext(context.WithValue(startCtx, ctxKey, "GET")) + assert.Nil(t, err) + assert.EqualErrorf(t, Parse(gr, &v), "GET: custom percent unset error", "custom unset error") + }) - pv := struct { - Name string `json:"name"` - Percent float64 `json:"percent"` - }{} - body := `{"name":"hello"}` - pr := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body)) - pr.Header.Set(ContentType, header.JsonContentType) - assert.EqualErrorf(t, Parse(pr, &pv), "custom percent unset error", "custom unset error") + t.Run("request post", func(t *testing.T) { + pv := struct { + Name string `json:"name"` + Percent float64 `json:"percent"` + }{} + body := `{"name":"hello"}` + pr := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body)) + pr.Header.Set(ContentType, header.JsonContentType) + pr = pr.WithContext(context.WithValue(startCtx, ctxKey, "POST")) + assert.EqualErrorf(t, Parse(pr, &pv), "POST: custom percent unset error", "custom unset error") + }) } func TestParseRequired(t *testing.T) { From 70d652ad19623ec5a61a5d8c6f7cdc779f9c0782 Mon Sep 17 00:00:00 2001 From: spectatorMrZ Date: Wed, 2 Aug 2023 05:21:15 +0800 Subject: [PATCH 6/6] test: add unmarshal test with CustomFieldUnsetErr --- core/mapping/unmarshaler_test.go | 38 +++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/core/mapping/unmarshaler_test.go b/core/mapping/unmarshaler_test.go index 16d6941875d5..aaf29979a5c7 100644 --- a/core/mapping/unmarshaler_test.go +++ b/core/mapping/unmarshaler_test.go @@ -4791,21 +4791,39 @@ func TestUnmarshalJsonBytesWithAnonymousFieldNotInOptions(t *testing.T) { func TestUnmarshalJsonBytesWithCustomFieldUnsetErr(t *testing.T) { type ( - Conf struct { + InnerConf struct { Name string `json:"name"` } + Conf struct { + Name string `json:"name"` + Inner InnerConf `json:"inner"` + } ) - var ( - input = []byte(`{}`) - c Conf - ) + t.Run("inner name unset", func(t *testing.T) { + var ( + input = []byte(`{"inner": {}, "name": "world"}`) + c Conf + ) - e := UnmarshalJsonBytes(input, &c, WithCustomFieldUnsetErr(func(field string) error { - // Here you can customize exceptions and internationalization processing - return fmt.Errorf("the key %s is unset", field) - })) - assert.Error(t, e) + e := UnmarshalJsonBytes(input, &c, WithCustomFieldUnsetErr(func(field string) error { + // Here you can customize exceptions and internationalization processing + return fmt.Errorf("the key %s is unset", field) + })) + assert.Error(t, e) + }) + + t.Run("name unset", func(t *testing.T) { + var ( + input = []byte(`{"inner": {"name":"hello"}`) + c Conf + ) + e := UnmarshalJsonBytes(input, &c, WithCustomFieldUnsetErr(func(field string) error { + // Here you can customize exceptions and internationalization processing + return fmt.Errorf("the key %s is unset", field) + })) + assert.Error(t, e) + }) } func TestUnmarshalNestedPtr(t *testing.T) {