Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4f789e7
feat: add custom unset err when parse request
SpectatorNan Aug 1, 2023
0f838eb
feat: custom unset error add context
SpectatorNan Aug 1, 2023
269bcfb
test: add unmarshal test with CustomFieldUnsetErr
SpectatorNan Aug 1, 2023
7a07ae8
feat: add custom unset err when parse request
SpectatorNan Aug 1, 2023
487de97
feat: custom unset error add context
SpectatorNan Aug 1, 2023
70d652a
test: add unmarshal test with CustomFieldUnsetErr
SpectatorNan Aug 1, 2023
bf2b7bb
Merge branch 'master' into support-request-custom-field-not-set-err
SpectatorNan Aug 15, 2023
80d1939
Merge branch 'support-request-custom-field-not-set-err' of https://gi…
SpectatorNan Aug 16, 2023
fa4a501
Merge branch 'zeromicro:master' into support-request-custom-field-not…
SpectatorNan Oct 2, 2023
5a1a8fd
Merge branch 'master' into support-request-custom-field-not-set-err
SpectatorNan Nov 2, 2023
6c2a0d2
Merge branch 'zeromicro:master' into support-request-custom-field-not…
SpectatorNan Jan 24, 2024
bf0d150
Merge branch 'master' into support-request-custom-field-not-set-err
SpectatorNan Apr 15, 2024
a2e579b
Merge branch 'master' into support-request-custom-field-not-set-err
SpectatorNan Jun 17, 2024
32cb9d5
Merge branch 'master' into support-request-custom-field-not-set-err
SpectatorNan Aug 3, 2024
b1fe325
Merge branch 'master' into support-request-custom-field-not-set-err
SpectatorNan Dec 26, 2024
b8a68ce
Merge branch 'master' into support-request-custom-field-not-set-err
SpectatorNan Mar 14, 2025
ca9c654
Merge branch 'master' into support-request-custom-field-not-set-err
SpectatorNan Apr 16, 2025
4a19f89
Merge branch 'master' into support-request-custom-field-not-set-err
SpectatorNan May 15, 2025
3549799
Merge branch 'master' into support-request-custom-field-not-set-err
SpectatorNan Nov 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions core/mapping/unmarshaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,13 @@ type (
// UnmarshalOption defines the method to customize an Unmarshaler.
UnmarshalOption func(*unmarshalOptions)

unmarshalOptions struct {
unmarshalOptions struct {
fillDefault bool
fromArray bool
fromString bool
opaqueKeys bool
canonicalKey func(key string) string
customFieldUnsetErr func(key string) error
}
)

Expand All @@ -71,7 +72,18 @@ func NewUnmarshaler(key string, opts ...UnmarshalOption) *Unmarshaler {
return &unmarshaler
}

// UnmarshalKey unmarshals m into v with the tag key.
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)
}
Expand Down Expand Up @@ -956,6 +968,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)
}

Expand All @@ -965,6 +980,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)
}
}
Expand Down Expand Up @@ -1046,6 +1064,13 @@ func WithCanonicalKeyFunc(f func(string) string) UnmarshalOption {
}
}

// WithCustomFieldUnsetErr customizes an Unmarshaler with custom field unset error.
func WithCustomFieldUnsetErr(f func(fullName 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) {
Expand Down
37 changes: 37 additions & 0 deletions core/mapping/unmarshaler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5506,6 +5506,43 @@ func TestUnmarshalJsonBytesWithAnonymousFieldNotInOptions(t *testing.T) {
assert.Error(t, UnmarshalJsonBytes(input, &c))
}

func TestUnmarshalJsonBytesWithCustomFieldUnsetErr(t *testing.T) {
type (
InnerConf struct {
Name string `json:"name"`
}
Conf struct {
Name string `json:"name"`
Inner InnerConf `json:"inner"`
}
)

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)
})

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) {
type inner struct {
Int **int `key:"int"`
Expand Down
34 changes: 27 additions & 7 deletions rest/httpx/requests.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package httpx

import (
"context"
"io"
"net/http"
"reflect"
Expand Down Expand Up @@ -33,11 +34,12 @@ var (
pathKey,
mapping.WithStringValues(),
mapping.WithOpaqueKeys())

// panic: sync/atomic: store of inconsistently typed value into Value
// don't use atomic.Value to store the validator, different concrete types still panic
validator Validator
validatorLock sync.RWMutex

customFieldUnsetErr func(ctx context.Context, key string) error
)

// Validator defines the interface for validating the request.
Expand Down Expand Up @@ -87,8 +89,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.
Expand All @@ -113,12 +115,13 @@ 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 {
opts := getUnmarshalOptions(r)
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.
Expand All @@ -129,8 +132,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.
Expand All @@ -151,3 +154,20 @@ func getValidator() Validator {
func withJsonBody(r *http.Request) bool {
return r.ContentLength > 0 && strings.Contains(r.Header.Get(header.ContentType), header.ApplicationJson)
}

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))
}
34 changes: 34 additions & 0 deletions rest/httpx/requests_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package httpx

import (
"context"
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"reflect"
Expand Down Expand Up @@ -566,6 +568,38 @@
})
}

func TestParseCustomUnsetErr(t *testing.T) {
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)
})
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)
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")
})

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)

Check failure on line 597 in rest/httpx/requests_test.go

View workflow job for this annotation

GitHub Actions / Windows

undefined: 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) {
v := struct {
Name string `form:"name"`
Expand Down
Loading