diff --git a/_typos.toml b/_typos.toml index 3d3103e19..bb673bac6 100644 --- a/_typos.toml +++ b/_typos.toml @@ -18,4 +18,8 @@ HeaderReferer = "HeaderReferer" expectedReferer = "expectedReferer" Referer = "Referer" O_WRONLY = "O_WRONLY" -WRONLY = "WRONLY" \ No newline at end of file +WRONLY = "WRONLY" +ome = "ome" +ifModifiedSice = "ifModifiedSice" +hd = "hd" +pn = "pn" \ No newline at end of file diff --git a/pkg/app/server/binding/binder_test.go b/pkg/app/server/binding/binder_test.go index 7919d8d60..225b8bd2e 100644 --- a/pkg/app/server/binding/binder_test.go +++ b/pkg/app/server/binding/binder_test.go @@ -417,10 +417,11 @@ func TestBind_ZeroValueBind(t *testing.T) { func TestBind_DefaultValueBind(t *testing.T) { var s struct { - A int `default:"15"` - B float64 `query:"b" default:"17"` - C []int `default:"15"` - D []string `default:"qwe"` + A int `default:"15"` + B float64 `query:"b" default:"17"` + C []int `default:"[15]"` + D []string `default:"['qwe','asd']"` + F [2]string `default:"['qwe','asd','zxc']"` } req := newMockRequest(). SetRequestURI("http://foobar.com") @@ -432,7 +433,23 @@ func TestBind_DefaultValueBind(t *testing.T) { assert.DeepEqual(t, 15, s.A) assert.DeepEqual(t, float64(17), s.B) assert.DeepEqual(t, 15, s.C[0]) + assert.DeepEqual(t, 2, len(s.D)) assert.DeepEqual(t, "qwe", s.D[0]) + assert.DeepEqual(t, "asd", s.D[1]) + assert.DeepEqual(t, 2, len(s.F)) + assert.DeepEqual(t, "qwe", s.F[0]) + assert.DeepEqual(t, "asd", s.F[1]) + + var s2 struct { + F [2]string `default:"['qwe']"` + } + err = DefaultBinder().Bind(req.Req, &s2, nil) + if err != nil { + t.Fatal(err) + } + assert.DeepEqual(t, 2, len(s2.F)) + assert.DeepEqual(t, "qwe", s2.F[0]) + assert.DeepEqual(t, "", s2.F[1]) var d struct { D [2]string `default:"qwe"` @@ -1549,6 +1566,32 @@ func TestBind_Issue1015(t *testing.T) { assert.DeepEqual(t, "asd", result.A) } +func TestBind_JSONWithDefault(t *testing.T) { + type Req struct { + J1 string `json:"j1" default:"j1default"` + } + + req := newMockRequest(). + SetJSONContentType(). + SetBody([]byte(`{"j1":"j1"}`)) + var result Req + err := DefaultBinder().Bind(req.Req, &result, nil) + if err != nil { + t.Error(err) + } + assert.DeepEqual(t, "j1", result.J1) + + result = Req{} + req = newMockRequest(). + SetJSONContentType(). + SetBody([]byte(`{"j2":"j2"}`)) + err = DefaultBinder().Bind(req.Req, &result, nil) + if err != nil { + t.Error(err) + } + assert.DeepEqual(t, "j1default", result.J1) +} + func TestBind_WithoutPreBindForTag(t *testing.T) { type BaseQuery struct { Action string `query:"Action" binding:"required"` diff --git a/pkg/app/server/binding/internal/decoder/base_type_decoder.go b/pkg/app/server/binding/internal/decoder/base_type_decoder.go index ece04f737..9c5cb1200 100644 --- a/pkg/app/server/binding/internal/decoder/base_type_decoder.go +++ b/pkg/app/server/binding/internal/decoder/base_type_decoder.go @@ -69,14 +69,17 @@ func (d *baseTypeFieldTextDecoder) Decode(req *protocol.Request, params param.Pa var defaultValue string for _, tagInfo := range d.tagInfos { if tagInfo.Skip || tagInfo.Key == jsonTag || tagInfo.Key == fileNameTag { - defaultValue = tagInfo.Default if tagInfo.Key == jsonTag { + defaultValue = tagInfo.Default found := checkRequireJSON(req, tagInfo) if found { err = nil } else { err = fmt.Errorf("'%s' field is a 'required' parameter, but the request body does not have this parameter '%s'", d.fieldName, tagInfo.JSONName) } + if len(tagInfo.Default) != 0 && keyExist(req, tagInfo) { + defaultValue = "" + } } continue } @@ -94,7 +97,7 @@ func (d *baseTypeFieldTextDecoder) Decode(req *protocol.Request, params param.Pa return err } if len(text) == 0 && len(defaultValue) != 0 { - text = defaultValue + text = toDefaultValue(d.fieldType, defaultValue) } if !exist && len(text) == 0 { return nil diff --git a/pkg/app/server/binding/internal/decoder/customized_type_decoder.go b/pkg/app/server/binding/internal/decoder/customized_type_decoder.go index 19efa46ae..ab343c811 100644 --- a/pkg/app/server/binding/internal/decoder/customized_type_decoder.go +++ b/pkg/app/server/binding/internal/decoder/customized_type_decoder.go @@ -60,7 +60,12 @@ func (d *customizedFieldTextDecoder) Decode(req *protocol.Request, params param. var defaultValue string for _, tagInfo := range d.tagInfos { if tagInfo.Skip || tagInfo.Key == jsonTag || tagInfo.Key == fileNameTag { - defaultValue = tagInfo.Default + if tagInfo.Key == jsonTag { + defaultValue = tagInfo.Default + if len(tagInfo.Default) != 0 && keyExist(req, tagInfo) { + defaultValue = "" + } + } continue } text, exist = tagInfo.Getter(req, params, tagInfo.Value) @@ -73,7 +78,7 @@ func (d *customizedFieldTextDecoder) Decode(req *protocol.Request, params param. return nil } if len(text) == 0 && len(defaultValue) != 0 { - text = defaultValue + text = toDefaultValue(d.fieldType, defaultValue) } v, err := d.decodeFunc(req, params, text) diff --git a/pkg/app/server/binding/internal/decoder/decoder.go b/pkg/app/server/binding/internal/decoder/decoder.go index f18a68127..4c36bcea9 100644 --- a/pkg/app/server/binding/internal/decoder/decoder.go +++ b/pkg/app/server/binding/internal/decoder/decoder.go @@ -124,7 +124,7 @@ func getFieldDecoder(pInfo parentInfos, field reflect.StructField, index int, by // JSONName is like 'a.b.c' for 'required validate' fieldTagInfos, newParentJSONName, needValidate := lookupFieldTags(field, pInfo.JSONName, config) if len(fieldTagInfos) == 0 && !config.DisableDefaultTag { - fieldTagInfos = getDefaultFieldTags(field) + fieldTagInfos, newParentJSONName = getDefaultFieldTags(field, pInfo.JSONName) } if len(byTag) != 0 { fieldTagInfos = getFieldTagInfoByTag(field, byTag) diff --git a/pkg/app/server/binding/internal/decoder/gjson_required.go b/pkg/app/server/binding/internal/decoder/gjson_required.go index 95e3c4fc5..130d65d8f 100644 --- a/pkg/app/server/binding/internal/decoder/gjson_required.go +++ b/pkg/app/server/binding/internal/decoder/gjson_required.go @@ -47,3 +47,12 @@ func checkRequireJSON(req *protocol.Request, tagInfo TagInfo) bool { } return true } + +func keyExist(req *protocol.Request, tagInfo TagInfo) bool { + ct := bytesconv.B2s(req.Header.ContentType()) + if utils.FilterContentType(ct) != consts.MIMEApplicationJSON { + return false + } + result := gjson.GetBytes(req.Body(), tagInfo.JSONName) + return result.Exists() +} diff --git a/pkg/app/server/binding/internal/decoder/map_type_decoder.go b/pkg/app/server/binding/internal/decoder/map_type_decoder.go index 31fe85a1b..59fed7716 100644 --- a/pkg/app/server/binding/internal/decoder/map_type_decoder.go +++ b/pkg/app/server/binding/internal/decoder/map_type_decoder.go @@ -61,14 +61,17 @@ func (d *mapTypeFieldTextDecoder) Decode(req *protocol.Request, params param.Par var defaultValue string for _, tagInfo := range d.tagInfos { if tagInfo.Skip || tagInfo.Key == jsonTag || tagInfo.Key == fileNameTag { - defaultValue = tagInfo.Default if tagInfo.Key == jsonTag { + defaultValue = tagInfo.Default found := checkRequireJSON(req, tagInfo) if found { err = nil } else { err = fmt.Errorf("'%s' field is a 'required' parameter, but the request does not have this parameter", d.fieldName) } + if len(tagInfo.Default) != 0 && keyExist(req, tagInfo) { + defaultValue = "" + } } continue } @@ -86,7 +89,7 @@ func (d *mapTypeFieldTextDecoder) Decode(req *protocol.Request, params param.Par return err } if len(text) == 0 && len(defaultValue) != 0 { - text = defaultValue + text = toDefaultValue(d.fieldType, defaultValue) } if !exist && len(text) == 0 { return nil diff --git a/pkg/app/server/binding/internal/decoder/slice_type_decoder.go b/pkg/app/server/binding/internal/decoder/slice_type_decoder.go index fc5c9814f..c2887d1c4 100644 --- a/pkg/app/server/binding/internal/decoder/slice_type_decoder.go +++ b/pkg/app/server/binding/internal/decoder/slice_type_decoder.go @@ -61,16 +61,20 @@ func (d *sliceTypeFieldTextDecoder) Decode(req *protocol.Request, params param.P var texts []string var defaultValue string var bindRawBody bool + var isDefault bool for _, tagInfo := range d.tagInfos { if tagInfo.Skip || tagInfo.Key == jsonTag || tagInfo.Key == fileNameTag { - defaultValue = tagInfo.Default if tagInfo.Key == jsonTag { + defaultValue = tagInfo.Default found := checkRequireJSON(req, tagInfo) if found { err = nil } else { err = fmt.Errorf("'%s' field is a 'required' parameter, but the request does not have this parameter", d.fieldName) } + if len(tagInfo.Default) != 0 && keyExist(req, tagInfo) { // + defaultValue = "" + } } continue } @@ -91,7 +95,9 @@ func (d *sliceTypeFieldTextDecoder) Decode(req *protocol.Request, params param.P return err } if len(texts) == 0 && len(defaultValue) != 0 { + defaultValue = toDefaultValue(d.fieldType, defaultValue) texts = append(texts, defaultValue) + isDefault = true } if len(texts) == 0 { return nil @@ -113,7 +119,7 @@ func (d *sliceTypeFieldTextDecoder) Decode(req *protocol.Request, params param.P } if d.isArray { - if len(texts) != field.Len() { + if len(texts) != field.Len() && !isDefault { return fmt.Errorf("%q is not valid value for %s", texts, field.Type().String()) } } else { @@ -135,6 +141,13 @@ func (d *sliceTypeFieldTextDecoder) Decode(req *protocol.Request, params param.P elemKind = t.Kind() ptrDepth++ } + if isDefault { + err = hJson.Unmarshal(bytesconv.S2b(texts[0]), reqValue.Field(d.index).Addr().Interface()) + if err != nil { + return fmt.Errorf("using '%s' to unmarshal field '%s: %s' failed, %v", texts[0], d.fieldName, d.fieldType.String(), err) + } + return nil + } for idx, text := range texts { var vv reflect.Value @@ -218,33 +231,3 @@ func getSliceFieldDecoder(field reflect.StructField, index int, tagInfos []TagIn isArray: isArray, }}, nil } - -func stringToValue(elemType reflect.Type, text string, req *protocol.Request, params param.Params, config *DecodeConfig) (v reflect.Value, err error) { - v = reflect.New(elemType).Elem() - if customizedFunc, exist := config.TypeUnmarshalFuncs[elemType]; exist { - val, err := customizedFunc(req, params, text) - if err != nil { - return reflect.Value{}, err - } - return val, nil - } - switch elemType.Kind() { - case reflect.Struct: - err = hJson.Unmarshal(bytesconv.S2b(text), v.Addr().Interface()) - case reflect.Map: - err = hJson.Unmarshal(bytesconv.S2b(text), v.Addr().Interface()) - case reflect.Array, reflect.Slice: - // do nothing - default: - decoder, err := SelectTextDecoder(elemType) - if err != nil { - return reflect.Value{}, fmt.Errorf("unsupported type %s for slice/array", elemType.String()) - } - err = decoder.UnmarshalString(text, v, config.LooseZeroMode) - if err != nil { - return reflect.Value{}, fmt.Errorf("unable to decode '%s' as %s: %w", text, elemType.String(), err) - } - } - - return v, err -} diff --git a/pkg/app/server/binding/internal/decoder/sonic_required.go b/pkg/app/server/binding/internal/decoder/sonic_required.go index e408901a9..61f3d8d68 100644 --- a/pkg/app/server/binding/internal/decoder/sonic_required.go +++ b/pkg/app/server/binding/internal/decoder/sonic_required.go @@ -60,3 +60,12 @@ func stringSliceForInterface(s string) (ret []interface{}) { } return } + +func keyExist(req *protocol.Request, tagInfo TagInfo) bool { + ct := bytesconv.B2s(req.Header.ContentType()) + if utils.FilterContentType(ct) != consts.MIMEApplicationJSON { + return false + } + node, _ := sonic.Get(req.Body(), stringSliceForInterface(tagInfo.JSONName)...) + return node.Exists() +} diff --git a/pkg/app/server/binding/internal/decoder/struct_type_decoder.go b/pkg/app/server/binding/internal/decoder/struct_type_decoder.go index 3030f2ac6..75f3ae4aa 100644 --- a/pkg/app/server/binding/internal/decoder/struct_type_decoder.go +++ b/pkg/app/server/binding/internal/decoder/struct_type_decoder.go @@ -38,14 +38,17 @@ func (d *structTypeFieldTextDecoder) Decode(req *protocol.Request, params param. var defaultValue string for _, tagInfo := range d.tagInfos { if tagInfo.Skip || tagInfo.Key == jsonTag || tagInfo.Key == fileNameTag { - defaultValue = tagInfo.Default if tagInfo.Key == jsonTag { + defaultValue = tagInfo.Default found := checkRequireJSON(req, tagInfo) if found { err = nil } else { err = fmt.Errorf("'%s' field is a 'required' parameter, but the request does not have this parameter", d.fieldName) } + if len(tagInfo.Default) != 0 && keyExist(req, tagInfo) { + defaultValue = "" + } } continue } @@ -63,7 +66,7 @@ func (d *structTypeFieldTextDecoder) Decode(req *protocol.Request, params param. return err } if len(text) == 0 && len(defaultValue) != 0 { - text = defaultValue + text = toDefaultValue(d.fieldType, defaultValue) } if !exist && len(text) == 0 { return nil diff --git a/pkg/app/server/binding/internal/decoder/tag.go b/pkg/app/server/binding/internal/decoder/tag.go index 6df09aaa3..8ca5ae0e6 100644 --- a/pkg/app/server/binding/internal/decoder/tag.go +++ b/pkg/app/server/binding/internal/decoder/tag.go @@ -87,7 +87,7 @@ func lookupFieldTags(field reflect.StructField, parentJSONName string, config *D tagValue = field.Name } skip := false - jsonName := "" + jsonName := parentJSONName + "." + field.Name if tag == jsonTag { jsonName = parentJSONName + "." + tagValue } @@ -120,7 +120,7 @@ func lookupFieldTags(field reflect.StructField, parentJSONName string, config *D return tagInfos, newParentJSONName, needValidate } -func getDefaultFieldTags(field reflect.StructField) (tagInfos []TagInfo) { +func getDefaultFieldTags(field reflect.StructField, parentJSONName string) (tagInfos []TagInfo, newParentJSONName string) { defaultVal := "" if val, ok := field.Tag.Lookup(defaultTag); ok { defaultVal = val @@ -128,8 +128,10 @@ func getDefaultFieldTags(field reflect.StructField) (tagInfos []TagInfo) { tags := []string{pathTag, formTag, queryTag, cookieTag, headerTag, jsonTag, fileNameTag} for _, tag := range tags { - tagInfos = append(tagInfos, TagInfo{Key: tag, Value: field.Name, Default: defaultVal}) + jsonName := strings.TrimPrefix(parentJSONName+"."+field.Name, ".") + tagInfos = append(tagInfos, TagInfo{Key: tag, Value: field.Name, Default: defaultVal, JSONName: jsonName}) } + newParentJSONName = strings.TrimPrefix(parentJSONName+"."+field.Name, ".") return } diff --git a/pkg/app/server/binding/internal/decoder/util.go b/pkg/app/server/binding/internal/decoder/util.go new file mode 100644 index 000000000..be141c282 --- /dev/null +++ b/pkg/app/server/binding/internal/decoder/util.go @@ -0,0 +1,76 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package decoder + +import ( + "fmt" + "reflect" + "strings" + + "github.com/cloudwego/hertz/internal/bytesconv" + hJson "github.com/cloudwego/hertz/pkg/common/json" + "github.com/cloudwego/hertz/pkg/protocol" + "github.com/cloudwego/hertz/pkg/route/param" +) + +const ( + specialChar = "\x07" +) + +// toDefaultValue will preprocess the default value and transfer it to be standard format +func toDefaultValue(typ reflect.Type, defaultValue string) string { + switch typ.Kind() { + case reflect.Slice, reflect.Array, reflect.Map, reflect.Struct: + // escape single quote and double quote, replace single quote with double quote + defaultValue = strings.Replace(defaultValue, `"`, `\"`, -1) + defaultValue = strings.Replace(defaultValue, `\'`, specialChar, -1) + defaultValue = strings.Replace(defaultValue, `'`, `"`, -1) + defaultValue = strings.Replace(defaultValue, specialChar, `'`, -1) + } + return defaultValue +} + +// stringToValue is used to dynamically create reflect.Value for 'text' +func stringToValue(elemType reflect.Type, text string, req *protocol.Request, params param.Params, config *DecodeConfig) (v reflect.Value, err error) { + v = reflect.New(elemType).Elem() + if customizedFunc, exist := config.TypeUnmarshalFuncs[elemType]; exist { + val, err := customizedFunc(req, params, text) + if err != nil { + return reflect.Value{}, err + } + return val, nil + } + switch elemType.Kind() { + case reflect.Struct: + err = hJson.Unmarshal(bytesconv.S2b(text), v.Addr().Interface()) + case reflect.Map: + err = hJson.Unmarshal(bytesconv.S2b(text), v.Addr().Interface()) + case reflect.Array, reflect.Slice: + // do nothing + default: + decoder, err := SelectTextDecoder(elemType) + if err != nil { + return reflect.Value{}, fmt.Errorf("unsupported type %s for slice/array", elemType.String()) + } + err = decoder.UnmarshalString(text, v, config.LooseZeroMode) + if err != nil { + return reflect.Value{}, fmt.Errorf("unable to decode '%s' as %s: %w", text, elemType.String(), err) + } + } + + return v, err +} diff --git a/pkg/app/server/binding/tagexpr_bind_test.go b/pkg/app/server/binding/tagexpr_bind_test.go index 82221745c..1533b5aea 100644 --- a/pkg/app/server/binding/tagexpr_bind_test.go +++ b/pkg/app/server/binding/tagexpr_bind_test.go @@ -539,38 +539,37 @@ func TestPath(t *testing.T) { assert.DeepEqual(t, (*int64)(nil), recv.Z) } -// FIXME: 复杂类型的默认值,暂时先不做,低优 func TestDefault(t *testing.T) { - //type S struct { - // SS string `json:"ss"` - //} + type S struct { + SS string `json:"ss"` + } type Recv struct { X **struct { - A []string `path:"a" json:"a"` - B int32 `path:"b" default:"32"` - C bool `json:"c" default:"true"` - D *float32 `default:"123.4"` - // E *[]string `default:"['a','b','c','d,e,f']"` - // F map[string]string `default:"{'a':'\"\\'1','\"b':'c','c':'2'}"` - // G map[string]int64 `default:"{'a':1,'b':2,'c':3}"` - // H map[string]float64 `default:"{'a':0.1,'b':1.2,'c':2.3}"` - // I map[string]float64 `default:"{'\"a\"':0.1,'b':1.2,'c':2.3}"` - Empty string `default:""` - Null string `default:""` - CommaSpace string `default:",a:c "` - Dash string `default:"-"` + A []string `path:"a" json:"a"` + B int32 `path:"b" default:"32"` + C bool `json:"c" default:"true"` + D *float32 `default:"123.4"` + E *[]string `default:"['a','b','c','d,e,f']"` + F map[string]string `default:"{'a':'\"\\'1','\"b':'c','c':'2'}"` + G map[string]int64 `default:"{'a':1,'b':2,'c':3}"` + H map[string]float64 `default:"{'a':0.1,'b':1.2,'c':2.3}"` + I map[string]float64 `default:"{'\"a\"':0.1,'b':1.2,'c':2.3}"` + Empty string `default:""` + Null string `default:""` + CommaSpace string `default:",a:c "` + Dash string `default:"-"` // InvalidInt int `default:"abc"` // InvalidMap map[string]string `default:"abc"` } - Y string `json:"y" default:"y1"` - Z int64 - W string `json:"w"` - // V []int64 `json:"u" default:"[1,2,3]"` - // U []float32 `json:"u" default:"[1.1,2,3]"` - T *string `json:"t" default:"t1"` - // S S `default:"{'ss':'test'}"` - // O *S `default:"{'ss':'test2'}"` - // Complex map[string][]map[string][]int64 `default:"{'a':[{'aa':[1,2,3], 'bb':[4,5]}],'b':[{}]}"` + Y string `json:"y" default:"y1"` + Z int64 + W string `json:"w"` + V []int64 `json:"v" default:"[1,2,3]"` + U []float32 `json:"u" default:"[1.1,2,3]"` + T *string `json:"t" default:"t1"` + S S `default:"{'ss':'test'}"` + O *S `default:"{'ss':'test2'}"` + Complex map[string][]map[string][]int64 `default:"{'a':[{'aa':[1,2,3], 'bb':[4,5]}],'b':[{}]}"` } bodyReader := strings.NewReader(`{ @@ -601,11 +600,11 @@ func TestDefault(t *testing.T) { assert.DeepEqual(t, int32(32), (**recv.X).B) assert.DeepEqual(t, true, (**recv.X).C) assert.DeepEqual(t, float32(123.4), *(**recv.X).D) - // assert.DeepEqual(t, []string{"a", "b", "c", "d,e,f"}, *(**recv.X).E) - // assert.DeepEqual(t, map[string]string{"a": "\"'1", "\"b": "c", "c": "2"}, (**recv.X).F) - // assert.DeepEqual(t, map[string]int64{"a": 1, "b": 2, "c": 3}, (**recv.X).G) - // assert.DeepEqual(t, map[string]float64{"a": 0.1, "b": 1.2, "c": 2.3}, (**recv.X).H) - // assert.DeepEqual(t, map[string]float64{"\"a\"": 0.1, "b": 1.2, "c": 2.3}, (**recv.X).I) + assert.DeepEqual(t, []string{"a", "b", "c", "d,e,f"}, *(**recv.X).E) + assert.DeepEqual(t, map[string]string{"a": "\"'1", "\"b": "c", "c": "2"}, (**recv.X).F) + assert.DeepEqual(t, map[string]int64{"a": 1, "b": 2, "c": 3}, (**recv.X).G) + assert.DeepEqual(t, map[string]float64{"a": 0.1, "b": 1.2, "c": 2.3}, (**recv.X).H) + assert.DeepEqual(t, map[string]float64{"\"a\"": 0.1, "b": 1.2, "c": 2.3}, (**recv.X).I) assert.DeepEqual(t, "", (**recv.X).Empty) assert.DeepEqual(t, "", (**recv.X).Null) assert.DeepEqual(t, ",a:c ", (**recv.X).CommaSpace) @@ -615,11 +614,11 @@ func TestDefault(t *testing.T) { assert.DeepEqual(t, "y1", recv.Y) assert.DeepEqual(t, "t1", *recv.T) assert.DeepEqual(t, int64(6), recv.Z) - // assert.DeepEqual(t, []int64{1, 2, 3}, recv.V) - // assert.DeepEqual(t, []float32{1.1, 2, 3}, recv.U) - // assert.DeepEqual(t, S{SS: "test"}, recv.S) - // assert.DeepEqual(t, &S{SS: "test2"}, recv.O) - // assert.DeepEqual(t, map[string][]map[string][]int64{"a": {{"aa": {1, 2, 3}, "bb": []int64{4, 5}}}, "b": {map[string][]int64{}}}, recv.Complex) + assert.DeepEqual(t, []int64{1, 2, 3}, recv.V) + assert.DeepEqual(t, []float32{1.1, 2, 3}, recv.U) + assert.DeepEqual(t, S{SS: "test"}, recv.S) + assert.DeepEqual(t, &S{SS: "test2"}, recv.O) + assert.DeepEqual(t, map[string][]map[string][]int64{"a": {{"aa": {1, 2, 3}, "bb": []int64{4, 5}}}, "b": {map[string][]int64{}}}, recv.Complex) } func TestAuto(t *testing.T) { @@ -1196,29 +1195,29 @@ func TestIssue26(t *testing.T) { assert.DeepEqual(t, recv, recv2) } -// FIXME: after 'json unmarshal', the default value will change it -//func TestDefault2(t *testing.T) { -// type Recv struct { -// X **struct { -// Dash string `default:"xxxx"` -// } -// } -// bodyReader := strings.NewReader(`{ -// "X": { -// "Dash": "hello Dash" -// } -// }`) -// header := make(http.Header) -// header.Set("Content-Type", consts.MIMEApplicationJSON) -// req := newRequest("", header, nil, bodyReader) -// recv := new(Recv) -// -// err := DefaultBinder().Bind(req.Req, nil, recv) -// if err != nil { -// t.Error(err) -// } -// assert.DeepEqual(t, "hello Dash", (**recv.X).Dash) -//} +// BUGFIX: after 'json unmarshal', the default value will change it +func TestDefault2(t *testing.T) { + type Recv struct { + X **struct { + Dash string `default:"xxxx"` + } + } + bodyReader := strings.NewReader(`{ + "X": { + "Dash": "hello Dash" + } + }`) + header := make(http.Header) + header.Set("Content-Type", consts.MIMEApplicationJSON) + req := newRequest("", header, nil, bodyReader) + recv := new(Recv) + + err := DefaultBinder().Bind(req.Req, recv, nil) + if err != nil { + t.Error(err) + } + assert.DeepEqual(t, "hello Dash", (**recv.X).Dash) +} type ( files map[string][]file