Kevin Wan 1 year ago committed by GitHub
parent 45fbd7dc35
commit cb3ffc76a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -49,6 +49,7 @@ type (
unmarshalOptions struct { unmarshalOptions struct {
fillDefault bool fillDefault bool
fromString bool fromString bool
opaqueKeys bool
canonicalKey func(key string) string canonicalKey func(key string) string
} }
) )
@ -494,7 +495,7 @@ func (u *Unmarshaler) processAnonymousStructFieldOptional(fieldType reflect.Type
return err return err
} }
_, hasValue := getValue(m, fieldKey) _, hasValue := getValue(m, fieldKey, u.opts.opaqueKeys)
if hasValue { if hasValue {
if !filled { if !filled {
filled = true filled = true
@ -737,7 +738,7 @@ func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect
} }
valuer := createValuer(m, opts) valuer := createValuer(m, opts)
mapValue, hasValue := getValue(valuer, canonicalKey) mapValue, hasValue := getValue(valuer, canonicalKey, u.opts.opaqueKeys)
// When fillDefault is used, m is a null value, hasValue must be false, all priority judgments fillDefault. // When fillDefault is used, m is a null value, hasValue must be false, all priority judgments fillDefault.
if u.opts.fillDefault { if u.opts.fillDefault {
@ -928,6 +929,14 @@ func WithDefault() UnmarshalOption {
} }
} }
// WithOpaqueKeys customizes an Unmarshaler with opaque keys.
// Opaque keys are keys that are not processed by the unmarshaler.
func WithOpaqueKeys() UnmarshalOption {
return func(opt *unmarshalOptions) {
opt.opaqueKeys = true
}
}
func createValuer(v valuerWithParent, opts *fieldOptionsWithContext) valuerWithParent { func createValuer(v valuerWithParent, opts *fieldOptionsWithContext) valuerWithParent {
if opts.inherit() { if opts.inherit() {
return recursiveValuer{ return recursiveValuer{
@ -1005,8 +1014,8 @@ func fillWithSameType(fieldType reflect.Type, value reflect.Value, mapValue any,
} }
// getValue gets the value for the specific key, the key can be in the format of parentKey.childKey // getValue gets the value for the specific key, the key can be in the format of parentKey.childKey
func getValue(m valuerWithParent, key string) (any, bool) { func getValue(m valuerWithParent, key string, opaque bool) (any, bool) {
keys := readKeys(key) keys := readKeys(key, opaque)
return getValueWithChainedKeys(m, keys) return getValueWithChainedKeys(m, keys)
} }
@ -1065,7 +1074,11 @@ func newTypeMismatchErrorWithHint(name, expectType, actualType string) error {
name, expectType, actualType) name, expectType, actualType)
} }
func readKeys(key string) []string { func readKeys(key string, opaque bool) []string {
if opaque {
return []string{key}
}
cacheKeysLock.Lock() cacheKeysLock.Lock()
keys, ok := cacheKeys[key] keys, ok := cacheKeys[key]
cacheKeysLock.Unlock() cacheKeysLock.Unlock()

@ -5092,6 +5092,21 @@ func TestUnmarshalFromStringSliceForTypeMismatch(t *testing.T) {
}, &v)) }, &v))
} }
func TestUnmarshalWithOpaqueKeys(t *testing.T) {
var v struct {
Opaque string `key:"opaque.key"`
Value string `key:"value"`
}
unmarshaler := NewUnmarshaler("key", WithOpaqueKeys())
if assert.NoError(t, unmarshaler.Unmarshal(map[string]any{
"opaque.key": "foo",
"value": "bar",
}, &v)) {
assert.Equal(t, "foo", v.Opaque)
assert.Equal(t, "bar", v.Value)
}
}
func BenchmarkDefaultValue(b *testing.B) { func BenchmarkDefaultValue(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
var a struct { var a struct {

@ -23,8 +23,8 @@ const (
) )
var ( var (
formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues()) formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues(), mapping.WithOpaqueKeys())
pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues()) pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues(), mapping.WithOpaqueKeys())
validator atomic.Value validator atomic.Value
) )

@ -326,6 +326,8 @@ func TestParseHeaders_Error(t *testing.T) {
func TestParseWithValidator(t *testing.T) { func TestParseWithValidator(t *testing.T) {
SetValidator(mockValidator{}) SetValidator(mockValidator{})
defer SetValidator(mockValidator{nop: true})
var v struct { var v struct {
Name string `form:"name"` Name string `form:"name"`
Age int `form:"age"` Age int `form:"age"`
@ -343,6 +345,8 @@ func TestParseWithValidator(t *testing.T) {
func TestParseWithValidatorWithError(t *testing.T) { func TestParseWithValidatorWithError(t *testing.T) {
SetValidator(mockValidator{}) SetValidator(mockValidator{})
defer SetValidator(mockValidator{nop: true})
var v struct { var v struct {
Name string `form:"name"` Name string `form:"name"`
Age int `form:"age"` Age int `form:"age"`
@ -356,12 +360,41 @@ func TestParseWithValidatorWithError(t *testing.T) {
func TestParseWithValidatorRequest(t *testing.T) { func TestParseWithValidatorRequest(t *testing.T) {
SetValidator(mockValidator{}) SetValidator(mockValidator{})
defer SetValidator(mockValidator{nop: true})
var v mockRequest var v mockRequest
r, err := http.NewRequest(http.MethodGet, "/a?&age=18", http.NoBody) r, err := http.NewRequest(http.MethodGet, "/a?&age=18", http.NoBody)
assert.Nil(t, err) assert.Nil(t, err)
assert.Error(t, Parse(r, &v)) assert.Error(t, Parse(r, &v))
} }
func TestParseFormWithDot(t *testing.T) {
var v struct {
Age int `form:"user.age"`
}
r, err := http.NewRequest(http.MethodGet, "/a?user.age=18", http.NoBody)
assert.Nil(t, err)
assert.NoError(t, Parse(r, &v))
assert.Equal(t, 18, v.Age)
}
func TestParsePathWithDot(t *testing.T) {
var v struct {
Name string `path:"name.val"`
Age int `path:"age.val"`
}
r := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
r = pathvar.WithVars(r, map[string]string{
"name.val": "foo",
"age.val": "18",
})
err := Parse(r, &v)
assert.Nil(t, err)
assert.Equal(t, "foo", v.Name)
assert.Equal(t, 18, v.Age)
}
func BenchmarkParseRaw(b *testing.B) { func BenchmarkParseRaw(b *testing.B) {
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello&age=18&percent=3.4", http.NoBody) r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello&age=18&percent=3.4", http.NoBody)
if err != nil { if err != nil {
@ -406,9 +439,15 @@ func BenchmarkParseAuto(b *testing.B) {
} }
} }
type mockValidator struct{} type mockValidator struct {
nop bool
}
func (m mockValidator) Validate(r *http.Request, data any) error { func (m mockValidator) Validate(r *http.Request, data any) error {
if m.nop {
return nil
}
if r.URL.Path == "/a" { if r.URL.Path == "/a" {
val := reflect.ValueOf(data).Elem().FieldByName("Name").String() val := reflect.ValueOf(data).Elem().FieldByName("Name").String()
if val != "hello" { if val != "hello" {

Loading…
Cancel
Save