diff --git a/pkg/app/server/binding/binder.go b/pkg/app/server/binding/binder.go index 835ba029b..09ed5e49f 100644 --- a/pkg/app/server/binding/binder.go +++ b/pkg/app/server/binding/binder.go @@ -41,17 +41,13 @@ package binding import ( + "github.com/cloudwego/hertz/pkg/app/server/binding/path" "github.com/cloudwego/hertz/pkg/protocol" ) -// PathParam parameter acquisition interface on the URL path -type PathParam interface { - Get(name string) (string, bool) -} - type Binder interface { Name() string - Bind(*protocol.Request, PathParam, interface{}) error + Bind(*protocol.Request, path.PathParam, interface{}) error } var DefaultBinder Binder = &defaultBinder{} diff --git a/pkg/app/server/binding/binder_test.go b/pkg/app/server/binding/binder_test.go index 2a5aa9c66..4025fef44 100644 --- a/pkg/app/server/binding/binder_test.go +++ b/pkg/app/server/binding/binder_test.go @@ -42,6 +42,7 @@ package binding import ( "fmt" + "github.com/cloudwego/hertz/pkg/app/server/binding/path" "mime/multipart" "testing" @@ -492,7 +493,7 @@ type CustomizedDecode struct { A string } -func (c *CustomizedDecode) CustomizedFieldDecode(req *protocol.Request, params PathParam) error { +func (c *CustomizedDecode) CustomizedFieldDecode(req *protocol.Request, params path.PathParam) error { q1 := req.URI().QueryArgs().Peek("a") if len(q1) == 0 { return fmt.Errorf("can be nil") diff --git a/pkg/app/server/binding/config.go b/pkg/app/server/binding/config.go new file mode 100644 index 000000000..b02e6e78b --- /dev/null +++ b/pkg/app/server/binding/config.go @@ -0,0 +1,15 @@ +package binding + +import ( + standardJson "encoding/json" + + hjson "github.com/cloudwego/hertz/pkg/common/json" +) + +func ResetJSONUnmarshaler(fn func(data []byte, v interface{}) error) { + hjson.Unmarshal = fn +} + +func ResetStdJSONUnmarshaler() { + ResetJSONUnmarshaler(standardJson.Unmarshal) +} diff --git a/pkg/app/server/binding/base_type_decoder.go b/pkg/app/server/binding/decoder/base_type_decoder.go similarity index 92% rename from pkg/app/server/binding/base_type_decoder.go rename to pkg/app/server/binding/decoder/base_type_decoder.go index 483a0a0cf..b48921930 100644 --- a/pkg/app/server/binding/base_type_decoder.go +++ b/pkg/app/server/binding/decoder/base_type_decoder.go @@ -38,13 +38,13 @@ * Modifications are Copyright 2022 CloudWeGo Authors */ -package binding +package decoder import ( "fmt" "reflect" - "github.com/cloudwego/hertz/pkg/app/server/binding/text_decoder" + path1 "github.com/cloudwego/hertz/pkg/app/server/binding/path" "github.com/cloudwego/hertz/pkg/common/utils" ) @@ -58,10 +58,10 @@ type fieldInfo struct { type baseTypeFieldTextDecoder struct { fieldInfo - decoder text_decoder.TextDecoder + decoder TextDecoder } -func (d *baseTypeFieldTextDecoder) Decode(req *bindRequest, params PathParam, reqValue reflect.Value) error { +func (d *baseTypeFieldTextDecoder) Decode(req *bindRequest, params path1.PathParam, reqValue reflect.Value) error { var err error var text string var defaultValue string @@ -122,7 +122,7 @@ func (d *baseTypeFieldTextDecoder) Decode(req *bindRequest, params PathParam, re return nil } -func getBaseTypeTextDecoder(field reflect.StructField, index int, tagInfos []TagInfo, parentIdx []int) ([]decoder, error) { +func getBaseTypeTextDecoder(field reflect.StructField, index int, tagInfos []TagInfo, parentIdx []int) ([]fieldDecoder, error) { for idx, tagInfo := range tagInfos { switch tagInfo.Key { case pathTag: @@ -150,12 +150,12 @@ func getBaseTypeTextDecoder(field reflect.StructField, index int, tagInfos []Tag fieldType = field.Type.Elem() } - textDecoder, err := text_decoder.SelectTextDecoder(fieldType) + textDecoder, err := SelectTextDecoder(fieldType) if err != nil { return nil, err } - fieldDecoder := &baseTypeFieldTextDecoder{ + return []fieldDecoder{&baseTypeFieldTextDecoder{ fieldInfo: fieldInfo{ index: index, parentIndex: parentIdx, @@ -164,7 +164,5 @@ func getBaseTypeTextDecoder(field reflect.StructField, index int, tagInfos []Tag fieldType: fieldType, }, decoder: textDecoder, - } - - return []decoder{fieldDecoder}, nil + }}, nil } diff --git a/pkg/app/server/binding/customized_type_decoder.go b/pkg/app/server/binding/decoder/customized_type_decoder.go similarity index 94% rename from pkg/app/server/binding/customized_type_decoder.go rename to pkg/app/server/binding/decoder/customized_type_decoder.go index 870252251..d8908f18d 100644 --- a/pkg/app/server/binding/customized_type_decoder.go +++ b/pkg/app/server/binding/decoder/customized_type_decoder.go @@ -38,17 +38,19 @@ * Modifications are Copyright 2022 CloudWeGo Authors */ -package binding +package decoder import ( "reflect" + + path1 "github.com/cloudwego/hertz/pkg/app/server/binding/path" ) type customizedFieldTextDecoder struct { fieldInfo } -func (d *customizedFieldTextDecoder) Decode(req *bindRequest, params PathParam, reqValue reflect.Value) error { +func (d *customizedFieldTextDecoder) Decode(req *bindRequest, params path1.PathParam, reqValue reflect.Value) error { var err error v := reflect.New(d.fieldType) decoder := v.Interface().(CustomizedFieldDecoder) diff --git a/pkg/app/server/binding/decoder.go b/pkg/app/server/binding/decoder/decoder.go similarity index 87% rename from pkg/app/server/binding/decoder.go rename to pkg/app/server/binding/decoder/decoder.go index 9b77598c3..3de28e8ca 100644 --- a/pkg/app/server/binding/decoder.go +++ b/pkg/app/server/binding/decoder/decoder.go @@ -38,7 +38,7 @@ * Modifications are Copyright 2022 CloudWeGo Authors */ -package binding +package decoder import ( "fmt" @@ -47,6 +47,7 @@ import ( "net/url" "reflect" + path1 "github.com/cloudwego/hertz/pkg/app/server/binding/path" "github.com/cloudwego/hertz/pkg/protocol" ) @@ -59,20 +60,20 @@ type bindRequest struct { Cookie []*http.Cookie } -type decoder interface { - Decode(req *bindRequest, params PathParam, reqValue reflect.Value) error +type fieldDecoder interface { + Decode(req *bindRequest, params path1.PathParam, reqValue reflect.Value) error } type CustomizedFieldDecoder interface { - CustomizedFieldDecode(req *protocol.Request, params PathParam) error + CustomizedFieldDecode(req *protocol.Request, params path1.PathParam) error } -type Decoder func(req *protocol.Request, params PathParam, rv reflect.Value) error +type Decoder func(req *protocol.Request, params path1.PathParam, rv reflect.Value) error var customizedFieldDecoderType = reflect.TypeOf((*CustomizedFieldDecoder)(nil)).Elem() -func getReqDecoder(rt reflect.Type) (Decoder, error) { - var decoders []decoder +func GetReqDecoder(rt reflect.Type) (Decoder, error) { + var decoders []fieldDecoder el := rt.Elem() if el.Kind() != reflect.Struct { @@ -95,7 +96,7 @@ func getReqDecoder(rt reflect.Type) (Decoder, error) { } } - return func(req *protocol.Request, params PathParam, rv reflect.Value) error { + return func(req *protocol.Request, params path1.PathParam, rv reflect.Value) error { bindReq := &bindRequest{ Req: req, } @@ -110,12 +111,12 @@ func getReqDecoder(rt reflect.Type) (Decoder, error) { }, nil } -func getFieldDecoder(field reflect.StructField, index int, parentIdx []int) ([]decoder, error) { +func getFieldDecoder(field reflect.StructField, index int, parentIdx []int) ([]fieldDecoder, error) { for field.Type.Kind() == reflect.Ptr { field.Type = field.Type.Elem() } if reflect.PtrTo(field.Type).Implements(customizedFieldDecoderType) { - return []decoder{&customizedFieldTextDecoder{ + return []fieldDecoder{&customizedFieldTextDecoder{ fieldInfo: fieldInfo{ index: index, parentIndex: parentIdx, @@ -139,7 +140,7 @@ func getFieldDecoder(field reflect.StructField, index int, parentIdx []int) ([]d } if field.Type.Kind() == reflect.Struct { - var decoders []decoder + var decoders []fieldDecoder el := field.Type // todo: built-in bindings for some common structs, code need to be optimized switch el { diff --git a/pkg/app/server/binding/getter.go b/pkg/app/server/binding/decoder/getter.go similarity index 81% rename from pkg/app/server/binding/getter.go rename to pkg/app/server/binding/decoder/getter.go index 1174e99d0..a6c293082 100644 --- a/pkg/app/server/binding/getter.go +++ b/pkg/app/server/binding/decoder/getter.go @@ -38,16 +38,18 @@ * Modifications are Copyright 2022 CloudWeGo Authors */ -package binding +package decoder import ( "net/http" "net/url" + + path1 "github.com/cloudwego/hertz/pkg/app/server/binding/path" ) -type getter func(req *bindRequest, params PathParam, key string, defaultValue ...string) (ret []string) +type getter func(req *bindRequest, params path1.PathParam, key string, defaultValue ...string) (ret []string) -func path(req *bindRequest, params PathParam, key string, defaultValue ...string) (ret []string) { +func path(req *bindRequest, params path1.PathParam, key string, defaultValue ...string) (ret []string) { var value string if params != nil { value, _ = params.Get(key) @@ -64,7 +66,7 @@ func path(req *bindRequest, params PathParam, key string, defaultValue ...string } // todo: Optimize 'postform' and 'multipart-form' -func form(req *bindRequest, params PathParam, key string, defaultValue ...string) (ret []string) { +func form(req *bindRequest, params path1.PathParam, key string, defaultValue ...string) (ret []string) { if req.Query == nil { req.Query = make(url.Values) req.Req.URI().QueryArgs().VisitAll(func(queryKey, value []byte) { @@ -116,7 +118,7 @@ func form(req *bindRequest, params PathParam, key string, defaultValue ...string return } -func query(req *bindRequest, params PathParam, key string, defaultValue ...string) (ret []string) { +func query(req *bindRequest, params path1.PathParam, key string, defaultValue ...string) (ret []string) { if req.Query == nil { req.Query = make(url.Values) req.Req.URI().QueryArgs().VisitAll(func(queryKey, value []byte) { @@ -135,7 +137,7 @@ func query(req *bindRequest, params PathParam, key string, defaultValue ...strin return } -func cookie(req *bindRequest, params PathParam, key string, defaultValue ...string) (ret []string) { +func cookie(req *bindRequest, params path1.PathParam, key string, defaultValue ...string) (ret []string) { if len(req.Cookie) == 0 { req.Req.Header.VisitAllCookie(func(cookieKey, value []byte) { req.Cookie = append(req.Cookie, &http.Cookie{ @@ -156,7 +158,7 @@ func cookie(req *bindRequest, params PathParam, key string, defaultValue ...stri return } -func header(req *bindRequest, params PathParam, key string, defaultValue ...string) (ret []string) { +func header(req *bindRequest, params path1.PathParam, key string, defaultValue ...string) (ret []string) { if req.Header == nil { req.Header = make(http.Header) req.Req.Header.VisitAll(func(headerKey, value []byte) { @@ -175,19 +177,19 @@ func header(req *bindRequest, params PathParam, key string, defaultValue ...stri return } -func json(req *bindRequest, params PathParam, key string, defaultValue ...string) (ret []string) { +func json(req *bindRequest, params path1.PathParam, key string, defaultValue ...string) (ret []string) { // do nothing return } -func rawBody(req *bindRequest, params PathParam, key string, defaultValue ...string) (ret []string) { +func rawBody(req *bindRequest, params path1.PathParam, key string, defaultValue ...string) (ret []string) { if req.Req.Header.ContentLength() > 0 { ret = append(ret, string(req.Req.Body())) } return } -func FileName(req *bindRequest, params PathParam, key string, defaultValue ...string) (ret []string) { +func fileName(req *bindRequest, params path1.PathParam, key string, defaultValue ...string) (ret []string) { // do nothing return } diff --git a/pkg/app/server/binding/map_type_decoder.go b/pkg/app/server/binding/decoder/map_type_decoder.go similarity index 91% rename from pkg/app/server/binding/map_type_decoder.go rename to pkg/app/server/binding/decoder/map_type_decoder.go index f7d6e1c83..37529e378 100644 --- a/pkg/app/server/binding/map_type_decoder.go +++ b/pkg/app/server/binding/decoder/map_type_decoder.go @@ -38,13 +38,15 @@ * Modifications are Copyright 2022 CloudWeGo Authors */ -package binding +package decoder import ( "fmt" "reflect" "github.com/cloudwego/hertz/internal/bytesconv" + path1 "github.com/cloudwego/hertz/pkg/app/server/binding/path" + hjson "github.com/cloudwego/hertz/pkg/common/json" "github.com/cloudwego/hertz/pkg/common/utils" ) @@ -52,7 +54,7 @@ type mapTypeFieldTextDecoder struct { fieldInfo } -func (d *mapTypeFieldTextDecoder) Decode(req *bindRequest, params PathParam, reqValue reflect.Value) error { +func (d *mapTypeFieldTextDecoder) Decode(req *bindRequest, params path1.PathParam, reqValue reflect.Value) error { var text string var defaultValue string for _, tagInfo := range d.tagInfos { @@ -94,7 +96,7 @@ func (d *mapTypeFieldTextDecoder) Decode(req *bindRequest, params PathParam, req return nil } - err := jsonUnmarshalFunc(bytesconv.S2b(text), field.Addr().Interface()) + err := hjson.Unmarshal(bytesconv.S2b(text), field.Addr().Interface()) if err != nil { return fmt.Errorf("unable to decode '%s' as %s: %w", text, d.fieldType.Name(), err) } @@ -102,7 +104,7 @@ func (d *mapTypeFieldTextDecoder) Decode(req *bindRequest, params PathParam, req return nil } -func getMapTypeTextDecoder(field reflect.StructField, index int, tagInfos []TagInfo, parentIdx []int) ([]decoder, error) { +func getMapTypeTextDecoder(field reflect.StructField, index int, tagInfos []TagInfo, parentIdx []int) ([]fieldDecoder, error) { for idx, tagInfo := range tagInfos { switch tagInfo.Key { case pathTag: @@ -129,7 +131,8 @@ func getMapTypeTextDecoder(field reflect.StructField, index int, tagInfos []TagI for field.Type.Kind() == reflect.Ptr { fieldType = field.Type.Elem() } - fieldDecoder := &mapTypeFieldTextDecoder{ + + return []fieldDecoder{&mapTypeFieldTextDecoder{ fieldInfo: fieldInfo{ index: index, parentIndex: parentIdx, @@ -137,7 +140,5 @@ func getMapTypeTextDecoder(field reflect.StructField, index int, tagInfos []TagI tagInfos: tagInfos, fieldType: fieldType, }, - } - - return []decoder{fieldDecoder}, nil + }}, nil } diff --git a/pkg/app/server/binding/multipart_file_decoder.go b/pkg/app/server/binding/decoder/multipart_file_decoder.go similarity index 91% rename from pkg/app/server/binding/multipart_file_decoder.go rename to pkg/app/server/binding/decoder/multipart_file_decoder.go index 6f08c7668..1c3a180d6 100644 --- a/pkg/app/server/binding/multipart_file_decoder.go +++ b/pkg/app/server/binding/decoder/multipart_file_decoder.go @@ -14,11 +14,13 @@ * limitations under the License. */ -package binding +package decoder import ( "fmt" "reflect" + + path1 "github.com/cloudwego/hertz/pkg/app/server/binding/path" ) type fileTypeDecoder struct { @@ -26,7 +28,7 @@ type fileTypeDecoder struct { isRepeated bool } -func (d *fileTypeDecoder) Decode(req *bindRequest, params PathParam, reqValue reflect.Value) error { +func (d *fileTypeDecoder) Decode(req *bindRequest, params path1.PathParam, reqValue reflect.Value) error { fieldValue := GetFieldValue(reqValue, d.parentIndex) field := fieldValue.Field(d.index) @@ -70,7 +72,7 @@ func (d *fileTypeDecoder) Decode(req *bindRequest, params PathParam, reqValue re return nil } -func (d *fileTypeDecoder) fileSliceDecode(req *bindRequest, params PathParam, reqValue reflect.Value) error { +func (d *fileTypeDecoder) fileSliceDecode(req *bindRequest, params path1.PathParam, reqValue reflect.Value) error { fieldValue := GetFieldValue(reqValue, d.parentIndex) field := fieldValue.Field(d.index) // 如果没值,需要为其建一个值 @@ -138,7 +140,7 @@ func (d *fileTypeDecoder) fileSliceDecode(req *bindRequest, params PathParam, re return nil } -func getMultipartFileDecoder(field reflect.StructField, index int, tagInfos []TagInfo, parentIdx []int) ([]decoder, error) { +func getMultipartFileDecoder(field reflect.StructField, index int, tagInfos []TagInfo, parentIdx []int) ([]fieldDecoder, error) { fieldType := field.Type for field.Type.Kind() == reflect.Ptr { fieldType = field.Type.Elem() @@ -148,7 +150,7 @@ func getMultipartFileDecoder(field reflect.StructField, index int, tagInfos []Ta isRepeated = true } - fieldDecoder := &fileTypeDecoder{ + return []fieldDecoder{&fileTypeDecoder{ fieldInfo: fieldInfo{ index: index, parentIndex: parentIdx, @@ -157,7 +159,5 @@ func getMultipartFileDecoder(field reflect.StructField, index int, tagInfos []Ta fieldType: fieldType, }, isRepeated: isRepeated, - } - - return []decoder{fieldDecoder}, nil + }}, nil } diff --git a/pkg/app/server/binding/text_decoder/unit.go b/pkg/app/server/binding/decoder/reflect.go similarity index 55% rename from pkg/app/server/binding/text_decoder/unit.go rename to pkg/app/server/binding/decoder/reflect.go index 1c3703b1c..dba448fd6 100644 --- a/pkg/app/server/binding/text_decoder/unit.go +++ b/pkg/app/server/binding/decoder/reflect.go @@ -38,22 +38,71 @@ * Modifications are Copyright 2022 CloudWeGo Authors */ -package text_decoder +package decoder import ( "reflect" - "strconv" ) -type uintDecoder struct { - bitSize int +// ReferenceValue convert T to *T, the ptrDepth is the count of '*'. +func ReferenceValue(v reflect.Value, ptrDepth int) reflect.Value { + switch { + case ptrDepth > 0: + for ; ptrDepth > 0; ptrDepth-- { + vv := reflect.New(v.Type()) + vv.Elem().Set(v) + v = vv + } + case ptrDepth < 0: + for ; ptrDepth < 0 && v.Kind() == reflect.Ptr; ptrDepth++ { + v = v.Elem() + } + } + return v +} + +func GetNonNilReferenceValue(v reflect.Value) (reflect.Value, int) { + var ptrDepth int + t := v.Type() + elemKind := t.Kind() + for elemKind == reflect.Ptr { + t = t.Elem() + elemKind = t.Kind() + ptrDepth++ + } + val := reflect.New(t).Elem() + return val, ptrDepth } -func (d *uintDecoder) UnmarshalString(s string, fieldValue reflect.Value) error { - v, err := strconv.ParseUint(s, 10, d.bitSize) - if err != nil { - return err +func GetFieldValue(reqValue reflect.Value, parentIndex []int) reflect.Value { + for _, idx := range parentIndex { + if reqValue.Kind() == reflect.Ptr && reqValue.IsNil() { + nonNilVal, ptrDepth := GetNonNilReferenceValue(reqValue) + reqValue.Set(ReferenceValue(nonNilVal, ptrDepth)) + } + for reqValue.Kind() == reflect.Ptr { + reqValue = reqValue.Elem() + } + reqValue = reqValue.Field(idx) + } + + // It is possible that the parent struct is also a pointer, + // so need to create a non-nil reflect.Value for it at runtime. + for reqValue.Kind() == reflect.Ptr { + if reqValue.IsNil() { + nonNilVal, ptrDepth := GetNonNilReferenceValue(reqValue) + reqValue.Set(ReferenceValue(nonNilVal, ptrDepth)) + } + reqValue = reqValue.Elem() } - fieldValue.SetUint(v) - return nil + + return reqValue +} + +func getElemType(t reflect.Type) reflect.Type { + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + + return t } diff --git a/pkg/app/server/binding/slice_type_decoder.go b/pkg/app/server/binding/decoder/slice_type_decoder.go similarity index 92% rename from pkg/app/server/binding/slice_type_decoder.go rename to pkg/app/server/binding/decoder/slice_type_decoder.go index a84547643..40aadf3c2 100644 --- a/pkg/app/server/binding/slice_type_decoder.go +++ b/pkg/app/server/binding/decoder/slice_type_decoder.go @@ -38,7 +38,7 @@ * Modifications are Copyright 2022 CloudWeGo Authors */ -package binding +package decoder import ( "fmt" @@ -46,7 +46,8 @@ import ( "reflect" "github.com/cloudwego/hertz/internal/bytesconv" - "github.com/cloudwego/hertz/pkg/app/server/binding/text_decoder" + path1 "github.com/cloudwego/hertz/pkg/app/server/binding/path" + hjson "github.com/cloudwego/hertz/pkg/common/json" "github.com/cloudwego/hertz/pkg/common/utils" ) @@ -55,7 +56,7 @@ type sliceTypeFieldTextDecoder struct { isArray bool } -func (d *sliceTypeFieldTextDecoder) Decode(req *bindRequest, params PathParam, reqValue reflect.Value) error { +func (d *sliceTypeFieldTextDecoder) Decode(req *bindRequest, params path1.PathParam, reqValue reflect.Value) error { var texts []string var defaultValue string for _, tagInfo := range d.tagInfos { @@ -126,7 +127,7 @@ func (d *sliceTypeFieldTextDecoder) Decode(req *bindRequest, params PathParam, r return nil } -func getSliceFieldDecoder(field reflect.StructField, index int, tagInfos []TagInfo, parentIdx []int) ([]decoder, error) { +func getSliceFieldDecoder(field reflect.StructField, index int, tagInfos []TagInfo, parentIdx []int) ([]fieldDecoder, error) { if !(field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Array) { return nil, fmt.Errorf("unexpected type %s, expected slice or array", field.Type.String()) } @@ -165,7 +166,7 @@ func getSliceFieldDecoder(field reflect.StructField, index int, tagInfos []TagIn return getMultipartFileDecoder(field, index, tagInfos, parentIdx) } - fieldDecoder := &sliceTypeFieldTextDecoder{ + return []fieldDecoder{&sliceTypeFieldTextDecoder{ fieldInfo: fieldInfo{ index: index, parentIndex: parentIdx, @@ -174,9 +175,7 @@ func getSliceFieldDecoder(field reflect.StructField, index int, tagInfos []TagIn fieldType: fieldType, }, isArray: isArray, - } - - return []decoder{fieldDecoder}, nil + }}, nil } func stringToValue(elemType reflect.Type, text string) (v reflect.Value, err error) { @@ -185,13 +184,13 @@ func stringToValue(elemType reflect.Type, text string) (v reflect.Value, err err switch elemType.Kind() { case reflect.Struct: - err = jsonUnmarshalFunc(bytesconv.S2b(text), v.Addr().Interface()) + err = hjson.Unmarshal(bytesconv.S2b(text), v.Addr().Interface()) case reflect.Map: - err = jsonUnmarshalFunc(bytesconv.S2b(text), v.Addr().Interface()) + err = hjson.Unmarshal(bytesconv.S2b(text), v.Addr().Interface()) case reflect.Array, reflect.Slice: // do nothing default: - decoder, err := text_decoder.SelectTextDecoder(elemType) + decoder, err := SelectTextDecoder(elemType) if err != nil { return reflect.Value{}, fmt.Errorf("unsupported type %s for slice/array", elemType.String()) } diff --git a/pkg/app/server/binding/tag.go b/pkg/app/server/binding/decoder/tag.go similarity index 99% rename from pkg/app/server/binding/tag.go rename to pkg/app/server/binding/decoder/tag.go index f44778d97..6ad002788 100644 --- a/pkg/app/server/binding/tag.go +++ b/pkg/app/server/binding/decoder/tag.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package binding +package decoder import ( "reflect" diff --git a/pkg/app/server/binding/text_decoder/text_decoder.go b/pkg/app/server/binding/decoder/text_decoder.go similarity index 74% rename from pkg/app/server/binding/text_decoder/text_decoder.go rename to pkg/app/server/binding/decoder/text_decoder.go index 08659aede..425c2ea46 100644 --- a/pkg/app/server/binding/text_decoder/text_decoder.go +++ b/pkg/app/server/binding/decoder/text_decoder.go @@ -38,11 +38,12 @@ * Modifications are Copyright 2022 CloudWeGo Authors */ -package text_decoder +package decoder import ( "fmt" "reflect" + "strconv" ) type TextDecoder interface { @@ -90,3 +91,60 @@ func SelectTextDecoder(rt reflect.Type) (TextDecoder, error) { return nil, fmt.Errorf("unsupported type " + rt.String()) } + +type boolDecoder struct{} + +func (d *boolDecoder) UnmarshalString(s string, fieldValue reflect.Value) error { + v, err := strconv.ParseBool(s) + if err != nil { + return err + } + fieldValue.SetBool(v) + return nil +} + +type floatDecoder struct { + bitSize int +} + +func (d *floatDecoder) UnmarshalString(s string, fieldValue reflect.Value) error { + v, err := strconv.ParseFloat(s, d.bitSize) + if err != nil { + return err + } + fieldValue.SetFloat(v) + return nil +} + +type intDecoder struct { + bitSize int +} + +func (d *intDecoder) UnmarshalString(s string, fieldValue reflect.Value) error { + v, err := strconv.ParseInt(s, 10, d.bitSize) + if err != nil { + return err + } + fieldValue.SetInt(v) + return nil +} + +type stringDecoder struct{} + +func (d *stringDecoder) UnmarshalString(s string, fieldValue reflect.Value) error { + fieldValue.SetString(s) + return nil +} + +type uintDecoder struct { + bitSize int +} + +func (d *uintDecoder) UnmarshalString(s string, fieldValue reflect.Value) error { + v, err := strconv.ParseUint(s, 10, d.bitSize) + if err != nil { + return err + } + fieldValue.SetUint(v) + return nil +} diff --git a/pkg/app/server/binding/default.go b/pkg/app/server/binding/default.go index 0e983191c..980bab634 100644 --- a/pkg/app/server/binding/default.go +++ b/pkg/app/server/binding/default.go @@ -62,10 +62,13 @@ package binding import ( "fmt" + hjson "github.com/cloudwego/hertz/pkg/common/json" "reflect" "sync" "github.com/cloudwego/hertz/internal/bytesconv" + "github.com/cloudwego/hertz/pkg/app/server/binding/decoder" + "github.com/cloudwego/hertz/pkg/app/server/binding/path" "github.com/cloudwego/hertz/pkg/protocol" "github.com/go-playground/validator/v10" "google.golang.org/protobuf/proto" @@ -79,7 +82,7 @@ func (b *defaultBinder) Name() string { return "hertz" } -func (b *defaultBinder) Bind(req *protocol.Request, params PathParam, v interface{}) error { +func (b *defaultBinder) Bind(req *protocol.Request, params path.PathParam, v interface{}) error { err := b.preBindBody(req, v) if err != nil { return err @@ -93,12 +96,12 @@ func (b *defaultBinder) Bind(req *protocol.Request, params PathParam, v interfac } cached, ok := b.decoderCache.Load(typeID) if ok { - // cached decoder, fast path - decoder := cached.(Decoder) + // cached fieldDecoder, fast path + decoder := cached.(decoder.Decoder) return decoder(req, params, rv.Elem()) } - decoder, err := getReqDecoder(rv.Type()) + decoder, err := decoder.GetReqDecoder(rv.Type()) if err != nil { return err } @@ -120,7 +123,7 @@ func (b *defaultBinder) preBindBody(req *protocol.Request, v interface{}) error switch bytesconv.B2s(req.Header.ContentType()) { case jsonContentTypeBytes: // todo: Aligning the gin, add "EnableDecoderUseNumber"/"EnableDecoderDisallowUnknownFields" interface - return jsonUnmarshalFunc(req.Body(), v) + return hjson.Unmarshal(req.Body(), v) case protobufContentType: msg, ok := v.(proto.Message) if !ok { diff --git a/pkg/app/server/binding/json.go b/pkg/app/server/binding/json.go deleted file mode 100644 index 24407fb58..000000000 --- a/pkg/app/server/binding/json.go +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2022 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. - * MIT License - * - * Copyright (c) 2019-present Fenny and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file may have been modified by CloudWeGo authors. All CloudWeGo - * Modifications are Copyright 2022 CloudWeGo Authors - */ - -package binding - -import ( - "encoding/json" - - hjson "github.com/cloudwego/hertz/pkg/common/json" -) - -// JSONUnmarshaler is the interface implemented by types -// that can unmarshal a JSON description of themselves. -type JSONUnmarshaler func(data []byte, v interface{}) error - -var jsonUnmarshalFunc JSONUnmarshaler - -func init() { - ResetJSONUnmarshaler(hjson.Unmarshal) -} - -func ResetJSONUnmarshaler(fn JSONUnmarshaler) { - jsonUnmarshalFunc = fn -} - -func ResetStdJSONUnmarshaler() { - ResetJSONUnmarshaler(json.Unmarshal) -} diff --git a/pkg/app/server/binding/path/path.go b/pkg/app/server/binding/path/path.go new file mode 100644 index 000000000..26ddc02d2 --- /dev/null +++ b/pkg/app/server/binding/path/path.go @@ -0,0 +1,6 @@ +package path + +// PathParam parameter acquisition interface on the URL path +type PathParam interface { + Get(name string) (string, bool) +} diff --git a/pkg/app/server/binding/reflect.go b/pkg/app/server/binding/reflect.go index 7b8933442..4d2e7f33d 100644 --- a/pkg/app/server/binding/reflect.go +++ b/pkg/app/server/binding/reflect.go @@ -47,7 +47,6 @@ import ( func valueAndTypeID(v interface{}) (reflect.Value, uintptr) { header := (*emptyInterface)(unsafe.Pointer(&v)) - rv := reflect.ValueOf(v) return rv, header.typeID } @@ -56,66 +55,3 @@ type emptyInterface struct { typeID uintptr dataPtr unsafe.Pointer } - -// ReferenceValue convert T to *T, the ptrDepth is the count of '*'. -func ReferenceValue(v reflect.Value, ptrDepth int) reflect.Value { - switch { - case ptrDepth > 0: - for ; ptrDepth > 0; ptrDepth-- { - vv := reflect.New(v.Type()) - vv.Elem().Set(v) - v = vv - } - case ptrDepth < 0: - for ; ptrDepth < 0 && v.Kind() == reflect.Ptr; ptrDepth++ { - v = v.Elem() - } - } - return v -} - -func GetNonNilReferenceValue(v reflect.Value) (reflect.Value, int) { - var ptrDepth int - t := v.Type() - elemKind := t.Kind() - for elemKind == reflect.Ptr { - t = t.Elem() - elemKind = t.Kind() - ptrDepth++ - } - val := reflect.New(t).Elem() - return val, ptrDepth -} - -func GetFieldValue(reqValue reflect.Value, parentIndex []int) reflect.Value { - for _, idx := range parentIndex { - if reqValue.Kind() == reflect.Ptr && reqValue.IsNil() { - nonNilVal, ptrDepth := GetNonNilReferenceValue(reqValue) - reqValue.Set(ReferenceValue(nonNilVal, ptrDepth)) - } - for reqValue.Kind() == reflect.Ptr { - reqValue = reqValue.Elem() - } - reqValue = reqValue.Field(idx) - } - - // It is possible that the parent struct is also a pointer, - // so need to create a non-nil reflect.Value for it at runtime. - for reqValue.Kind() == reflect.Ptr { - if reqValue.IsNil() { - nonNilVal, ptrDepth := GetNonNilReferenceValue(reqValue) - reqValue.Set(ReferenceValue(nonNilVal, ptrDepth)) - } - reqValue = reqValue.Elem() - } - - return reqValue -} - -func getElemType(t reflect.Type) reflect.Type { - for t.Kind() == reflect.Ptr { - t = t.Elem() - } - - return t -} diff --git a/pkg/app/server/binding/text_decoder/bool.go b/pkg/app/server/binding/text_decoder/bool.go deleted file mode 100644 index 5ae167296..000000000 --- a/pkg/app/server/binding/text_decoder/bool.go +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2022 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. - * MIT License - * - * Copyright (c) 2019-present Fenny and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file may have been modified by CloudWeGo authors. All CloudWeGo - * Modifications are Copyright 2022 CloudWeGo Authors - */ - -package text_decoder - -import ( - "reflect" - "strconv" -) - -type boolDecoder struct{} - -func (d *boolDecoder) UnmarshalString(s string, fieldValue reflect.Value) error { - v, err := strconv.ParseBool(s) - if err != nil { - return err - } - fieldValue.SetBool(v) - return nil -} diff --git a/pkg/app/server/binding/text_decoder/float.go b/pkg/app/server/binding/text_decoder/float.go deleted file mode 100644 index f44a1c76d..000000000 --- a/pkg/app/server/binding/text_decoder/float.go +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2022 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. - * MIT License - * - * Copyright (c) 2019-present Fenny and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file may have been modified by CloudWeGo authors. All CloudWeGo - * Modifications are Copyright 2022 CloudWeGo Authors - */ - -package text_decoder - -import ( - "reflect" - "strconv" -) - -type floatDecoder struct { - bitSize int -} - -func (d *floatDecoder) UnmarshalString(s string, fieldValue reflect.Value) error { - v, err := strconv.ParseFloat(s, d.bitSize) - if err != nil { - return err - } - fieldValue.SetFloat(v) - return nil -} diff --git a/pkg/app/server/binding/text_decoder/int.go b/pkg/app/server/binding/text_decoder/int.go deleted file mode 100644 index 1594e2016..000000000 --- a/pkg/app/server/binding/text_decoder/int.go +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2022 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. - * MIT License - * - * Copyright (c) 2019-present Fenny and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file may have been modified by CloudWeGo authors. All CloudWeGo - * Modifications are Copyright 2022 CloudWeGo Authors - */ - -package text_decoder - -import ( - "reflect" - "strconv" -) - -type intDecoder struct { - bitSize int -} - -func (d *intDecoder) UnmarshalString(s string, fieldValue reflect.Value) error { - v, err := strconv.ParseInt(s, 10, d.bitSize) - if err != nil { - return err - } - fieldValue.SetInt(v) - return nil -} diff --git a/pkg/app/server/binding/text_decoder/string.go b/pkg/app/server/binding/text_decoder/string.go deleted file mode 100644 index 46917469f..000000000 --- a/pkg/app/server/binding/text_decoder/string.go +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2022 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. - * MIT License - * - * Copyright (c) 2019-present Fenny and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file may have been modified by CloudWeGo authors. All CloudWeGo - * Modifications are Copyright 2022 CloudWeGo Authors - */ - -package text_decoder - -import "reflect" - -type stringDecoder struct{} - -func (d *stringDecoder) UnmarshalString(s string, fieldValue reflect.Value) error { - fieldValue.SetString(s) - return nil -}