diff --git a/go.mod b/go.mod index 35e5f864b..e39cba638 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,11 @@ module github.com/cloudwego/hertz go 1.16 require ( - github.com/bytedance/go-tagexpr/v2 v2.9.2 github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7 github.com/bytedance/sonic v1.5.0 github.com/cloudwego/netpoll v0.3.1 github.com/fsnotify/fsnotify v1.5.4 github.com/go-playground/validator/v10 v10.11.2 - github.com/tidwall/gjson v1.13.0 // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 golang.org/x/sys v0.4.0 google.golang.org/protobuf v1.27.1 diff --git a/go.sum b/go.sum index a63317cdf..822d5e626 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/bytedance/go-tagexpr/v2 v2.9.2 h1:QySJaAIQgOEDQBLS3x9BxOWrnhqu5sQ+f6HaZIxD39I= -github.com/bytedance/go-tagexpr/v2 v2.9.2/go.mod h1:5qsx05dYOiUXOUgnQ7w3Oz8BYs2qtM/bJokdLb79wRM= github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7 h1:PtwsQyQJGxf8iaPptPNaduEIu9BnrNms+pcRdHAxZaM= github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q= github.com/bytedance/sonic v1.5.0 h1:XWdTi8bwPgxIML+eNV1IwNuTROK6EUrQ65ey8yd6fRQ= @@ -22,16 +20,9 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/henrylee2cn/ameda v1.4.8/go.mod h1:liZulR8DgHxdK+MEwvZIylGnmcjzQ6N6f2PlWe7nEO4= -github.com/henrylee2cn/ameda v1.4.10 h1:JdvI2Ekq7tapdPsuhrc4CaFiqw6QXFvZIULWJgQyCAk= -github.com/henrylee2cn/ameda v1.4.10/go.mod h1:liZulR8DgHxdK+MEwvZIylGnmcjzQ6N6f2PlWe7nEO4= -github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8 h1:yE9ULgp02BhYIrO6sdV/FPe0xQM6fNHkVQW2IAymfM0= -github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8/go.mod h1:Nhe/DM3671a5udlv2AdV2ni/MZzgfv2qrPL5nIi3EGQ= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -42,26 +33,15 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/nyaruka/phonenumbers v1.0.55 h1:bj0nTO88Y68KeUQ/n3Lo2KgK7lM1hF7L9NFuwcCl3yg= -github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.13.0 h1:3TFY9yxOQShrvmjdM76K+jc66zJeT6D3/VFFYCGQf7M= -github.com/tidwall/gjson v1.13.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -111,7 +91,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/app/context.go b/pkg/app/context.go index d7e114bde..5b73c0573 100644 --- a/pkg/app/context.go +++ b/pkg/app/context.go @@ -54,10 +54,9 @@ import ( "sync" "time" - "github.com/cloudwego/hertz/pkg/app/server/binding_v2" - "github.com/cloudwego/hertz/internal/bytesconv" "github.com/cloudwego/hertz/internal/bytestr" + "github.com/cloudwego/hertz/pkg/app/server/binding" "github.com/cloudwego/hertz/pkg/app/server/render" "github.com/cloudwego/hertz/pkg/common/errors" "github.com/cloudwego/hertz/pkg/common/tracer/traceinfo" @@ -1222,22 +1221,22 @@ func bodyAllowedForStatus(status int) bool { // BindAndValidate binds data from *RequestContext to obj and validates them if needed. // NOTE: obj should be a pointer. func (ctx *RequestContext) BindAndValidate(obj interface{}) error { - err := binding_v2.DefaultBinder.Bind(&ctx.Request, ctx.Params, obj) + err := binding.DefaultBinder.Bind(&ctx.Request, ctx.Params, obj) if err != nil { return err } - err = binding_v2.DefaultValidator.ValidateStruct(obj) + err = binding.DefaultValidator.ValidateStruct(obj) return err } // Bind binds data from *RequestContext to obj. // NOTE: obj should be a pointer. func (ctx *RequestContext) Bind(obj interface{}) error { - return binding_v2.DefaultBinder.Bind(&ctx.Request, ctx.Params, obj) + return binding.DefaultBinder.Bind(&ctx.Request, ctx.Params, obj) } // Validate validates obj with "vd" tag // NOTE: obj should be a pointer. func (ctx *RequestContext) Validate(obj interface{}) error { - return binding_v2.DefaultValidator.ValidateStruct(obj) + return binding.DefaultValidator.ValidateStruct(obj) } diff --git a/pkg/app/context_test.go b/pkg/app/context_test.go index fc3c147d8..bfe84486d 100644 --- a/pkg/app/context_test.go +++ b/pkg/app/context_test.go @@ -1169,7 +1169,7 @@ func TestRequestContext_GetResponse(t *testing.T) { func TestBindAndValidate(t *testing.T) { type Test struct { A string `query:"a"` - B int `query:"b" vd:"$>10"` + B int `query:"b" validate:"gt=10"` } c := &RequestContext{} diff --git a/pkg/app/server/binding_v2/base_type_decoder.go b/pkg/app/server/binding/base_type_decoder.go similarity index 58% rename from pkg/app/server/binding_v2/base_type_decoder.go rename to pkg/app/server/binding/base_type_decoder.go index 57e7aa958..69fdb98bd 100644 --- a/pkg/app/server/binding_v2/base_type_decoder.go +++ b/pkg/app/server/binding/base_type_decoder.go @@ -1,10 +1,50 @@ -package binding_v2 +/* + * 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 ( "fmt" "reflect" - "github.com/cloudwego/hertz/pkg/app/server/binding_v2/text_decoder" + "github.com/cloudwego/hertz/pkg/app/server/binding/text_decoder" "github.com/cloudwego/hertz/pkg/common/utils" "github.com/cloudwego/hertz/pkg/protocol" ) @@ -26,7 +66,6 @@ func (d *baseTypeFieldTextDecoder) Decode(req *protocol.Request, params PathPara var err error var text string var defaultValue string - // 最大努力交付,对齐 hertz 现有设计 for _, tagInfo := range d.tagInfos { if tagInfo.Key == jsonTag { continue @@ -37,7 +76,6 @@ func (d *baseTypeFieldTextDecoder) Decode(req *protocol.Request, params PathPara ret := tagInfo.Getter(req, params, tagInfo.Value) defaultValue = tagInfo.Default if len(ret) != 0 { - // 非数组/切片类型,只取第一个值作为只 text = ret[0] err = nil break @@ -56,12 +94,10 @@ func (d *baseTypeFieldTextDecoder) Decode(req *protocol.Request, params PathPara return nil } - // 得到该field的非nil值 + // get the non-nil value for the field reqValue = GetFieldValue(reqValue, d.parentIndex) - // 根据最终的 Struct,获取对应 field 的 reflect.Value field := reqValue.Field(d.index) if field.Kind() == reflect.Ptr { - // 如果是指针则新建一个reflect.Value,然后赋值给指针 t := field.Type() var ptrDepth int for t.Kind() == reflect.Ptr { diff --git a/pkg/app/server/binding/binder.go b/pkg/app/server/binding/binder.go new file mode 100644 index 000000000..d6fbda809 --- /dev/null +++ b/pkg/app/server/binding/binder.go @@ -0,0 +1,57 @@ +/* + * 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 ( + "github.com/cloudwego/hertz/pkg/protocol" +) + +// PathParams parameter acquisition interface on the URL path +type PathParams interface { + Get(name string) (string, bool) +} + +type Binder interface { + Name() string + Bind(*protocol.Request, PathParams, interface{}) error +} + +var DefaultBinder Binder = &Bind{} diff --git a/pkg/app/server/binding_v2/binder_test.go b/pkg/app/server/binding/binder_test.go similarity index 86% rename from pkg/app/server/binding_v2/binder_test.go rename to pkg/app/server/binding/binder_test.go index 11315ca1c..d228cfd57 100644 --- a/pkg/app/server/binding_v2/binder_test.go +++ b/pkg/app/server/binding/binder_test.go @@ -1,10 +1,49 @@ -package binding_v2 +/* + * 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 ( "fmt" "testing" - "github.com/cloudwego/hertz/pkg/app/server/binding" "github.com/cloudwego/hertz/pkg/common/test/assert" "github.com/cloudwego/hertz/pkg/protocol" "github.com/cloudwego/hertz/pkg/route/param" @@ -412,7 +451,6 @@ func TestBind_TypedefType(t *testing.T) { assert.DeepEqual(t, "1", s.T1.T1) } -// 枚举类型BaseType type EnumType int64 const ( @@ -484,9 +522,8 @@ func TestBind_CustomizedTypeDecode(t *testing.T) { func TestBind_JSON(t *testing.T) { type Req struct { - J1 string `json:"j1"` - J2 int `json:"j2" query:"j2"` // 1. json unmarshal 2. query binding cover - // todo: map + J1 string `json:"j1"` + J2 int `json:"j2" query:"j2"` // 1. json unmarshal 2. query binding cover J3 []byte `json:"j3"` J4 [2]string `json:"j4"` } @@ -541,7 +578,7 @@ func TestBind_ResetJSONUnmarshal(t *testing.T) { } } -func Benchmark_V2(b *testing.B) { +func Benchmark_Binding(b *testing.B) { type Req struct { Version string `path:"v"` ID int `query:"id"` @@ -582,44 +619,3 @@ func Benchmark_V2(b *testing.B) { } } } - -func Benchmark_V1(b *testing.B) { - type Req struct { - Version string `path:"v"` - ID int `query:"id"` - Header string `header:"h"` - Form string `form:"f"` - } - - req := newMockRequest(). - SetRequestURI("http://foobar.com?id=12"). - SetHeaders("h", "header"). - SetPostArg("f", "form"). - SetUrlEncodeContentType() - var params param.Params - params = append(params, param.Param{ - Key: "v", - Value: "1", - }) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - var result Req - err := binding.Bind(req.Req, &result, params) - if err != nil { - b.Error(err) - } - if result.ID != 12 { - b.Error("Id failed") - } - if result.Form != "form" { - b.Error("form failed") - } - if result.Header != "header" { - b.Error("header failed") - } - if result.Version != "1" { - b.Error("path failed") - } - } -} diff --git a/pkg/app/server/binding/binding.go b/pkg/app/server/binding/binding.go deleted file mode 100644 index fa4af9d97..000000000 --- a/pkg/app/server/binding/binding.go +++ /dev/null @@ -1,122 +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. - */ - -package binding - -import ( - "encoding/json" - "reflect" - - "github.com/bytedance/go-tagexpr/v2/binding" - "github.com/bytedance/go-tagexpr/v2/binding/gjson" - "github.com/bytedance/go-tagexpr/v2/validator" - hjson "github.com/cloudwego/hertz/pkg/common/json" - "github.com/cloudwego/hertz/pkg/protocol" - "github.com/cloudwego/hertz/pkg/route/param" -) - -func init() { - binding.ResetJSONUnmarshaler(hjson.Unmarshal) -} - -var defaultBinder = binding.Default() - -// BindAndValidate binds data from *protocol.Request to obj and validates them if needed. -// NOTE: -// -// obj should be a pointer. -func BindAndValidate(req *protocol.Request, obj interface{}, pathParams param.Params) error { - return defaultBinder.IBindAndValidate(obj, wrapRequest(req), pathParams) -} - -// Bind binds data from *protocol.Request to obj. -// NOTE: -// -// obj should be a pointer. -func Bind(req *protocol.Request, obj interface{}, pathParams param.Params) error { - return defaultBinder.IBind(obj, wrapRequest(req), pathParams) -} - -// Validate validates obj with "vd" tag -// NOTE: -// -// obj should be a pointer. -// Validate should be called after Bind. -func Validate(obj interface{}) error { - return defaultBinder.Validate(obj) -} - -// SetLooseZeroMode if set to true, -// the empty string request parameter is bound to the zero value of parameter. -// NOTE: -// -// The default is false. -// Suitable for these parameter types: query/header/cookie/form . -func SetLooseZeroMode(enable bool) { - defaultBinder.SetLooseZeroMode(enable) -} - -// SetErrorFactory customizes the factory of validation error. -// NOTE: -// -// If errFactory==nil, the default is used. -// SetErrorFactory will remain in effect once it has been called. -func SetErrorFactory(bindErrFactory, validatingErrFactory func(failField, msg string) error) { - defaultBinder.SetErrorFactory(bindErrFactory, validatingErrFactory) -} - -// MustRegTypeUnmarshal registers unmarshal function of type. -// NOTE: -// -// It will panic if exist error. -// MustRegTypeUnmarshal will remain in effect once it has been called. -func MustRegTypeUnmarshal(t reflect.Type, fn func(v string, emptyAsZero bool) (reflect.Value, error)) { - binding.MustRegTypeUnmarshal(t, fn) -} - -// MustRegValidateFunc registers validator function expression. -// NOTE: -// -// If force=true, allow to cover the existed same funcName. -// MustRegValidateFunc will remain in effect once it has been called. -func MustRegValidateFunc(funcName string, fn func(args ...interface{}) error, force ...bool) { - validator.RegFunc(funcName, fn, force...) -} - -// UseStdJSONUnmarshaler uses encoding/json as json library -// NOTE: -// -// The current version uses encoding/json by default. -// UseStdJSONUnmarshaler will remain in effect once it has been called. -func UseStdJSONUnmarshaler() { - binding.ResetJSONUnmarshaler(json.Unmarshal) -} - -// UseGJSONUnmarshaler uses github.com/bytedance/go-tagexpr/v2/binding/gjson as json library -// NOTE: -// -// UseGJSONUnmarshaler will remain in effect once it has been called. -func UseGJSONUnmarshaler() { - gjson.UseJSONUnmarshaler() -} - -// UseThirdPartyJSONUnmarshaler uses third-party json library for binding -// NOTE: -// -// UseThirdPartyJSONUnmarshaler will remain in effect once it has been called. -func UseThirdPartyJSONUnmarshaler(unmarshaler func(data []byte, v interface{}) error) { - binding.ResetJSONUnmarshaler(unmarshaler) -} diff --git a/pkg/app/server/binding/binding_test.go b/pkg/app/server/binding/binding_test.go deleted file mode 100644 index 84c5980c5..000000000 --- a/pkg/app/server/binding/binding_test.go +++ /dev/null @@ -1,437 +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. - */ - -package binding - -import ( - "bytes" - "fmt" - "mime/multipart" - "reflect" - "testing" - - "github.com/cloudwego/hertz/pkg/common/test/assert" - "github.com/cloudwego/hertz/pkg/protocol" - "github.com/cloudwego/hertz/pkg/route/param" -) - -func TestBindAndValidate(t *testing.T) { - type TestBind struct { - A string `query:"a"` - B []string `query:"b"` - C string `query:"c"` - D string `header:"d"` - E string `path:"e"` - F string `form:"f"` - G multipart.FileHeader `form:"g"` - H string `cookie:"h"` - } - - s := `------WebKitFormBoundaryJwfATyF8tmxSJnLg -Content-Disposition: form-data; name="f" - -fff -------WebKitFormBoundaryJwfATyF8tmxSJnLg -Content-Disposition: form-data; name="g"; filename="TODO" -Content-Type: application/octet-stream - -- SessionClient with referer and cookies support. -- Client with requests' pipelining support. -- ProxyHandler similar to FSHandler. -- WebSockets. See https://tools.ietf.org/html/rfc6455 . -- HTTP/2.0. See https://tools.ietf.org/html/rfc7540 . - -------WebKitFormBoundaryJwfATyF8tmxSJnLg-- -tailfoobar` - - mr := bytes.NewBufferString(s) - r := protocol.NewRequest("POST", "/foo", mr) - r.SetRequestURI("/foo/bar?a=aaa&b=b1&b=b2&c&i=19") - r.SetHeader("d", "ddd") - r.Header.SetContentLength(len(s)) - r.Header.SetContentTypeBytes([]byte("multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg")) - - r.SetCookie("h", "hhh") - - para := param.Params{ - {Key: "e", Value: "eee"}, - } - - // test BindAndValidate() - SetLooseZeroMode(true) - var req TestBind - err := BindAndValidate(r, &req, para) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - assert.DeepEqual(t, "aaa", req.A) - assert.DeepEqual(t, 2, len(req.B)) - assert.DeepEqual(t, "", req.C) - assert.DeepEqual(t, "ddd", req.D) - assert.DeepEqual(t, "eee", req.E) - assert.DeepEqual(t, "fff", req.F) - assert.DeepEqual(t, "TODO", req.G.Filename) - assert.DeepEqual(t, "hhh", req.H) - - // test Bind() - req = TestBind{} - err = Bind(r, &req, para) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - assert.DeepEqual(t, "aaa", req.A) - assert.DeepEqual(t, 2, len(req.B)) - assert.DeepEqual(t, "", req.C) - assert.DeepEqual(t, "ddd", req.D) - assert.DeepEqual(t, "eee", req.E) - assert.DeepEqual(t, "fff", req.F) - assert.DeepEqual(t, "TODO", req.G.Filename) - assert.DeepEqual(t, "hhh", req.H) - - type TestValidate struct { - I int `query:"i" vd:"$>20"` - } - - // test BindAndValidate() - var bindReq TestValidate - err = BindAndValidate(r, &bindReq, para) - if err == nil { - t.Fatalf("unexpected nil, expected an error") - } - - // test Validate() - bindReq = TestValidate{} - err = Bind(r, &bindReq, para) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - assert.DeepEqual(t, 19, bindReq.I) - err = Validate(&bindReq) - if err == nil { - t.Fatalf("unexpected nil, expected an error") - } -} - -func TestJsonBind(t *testing.T) { - type Test struct { - A string `json:"a"` - B []string `json:"b"` - C string `json:"c"` - D int `json:"d,string"` - } - - data := `{"a":"aaa", "b":["b1","b2"], "c":"ccc", "d":"100"}` - mr := bytes.NewBufferString(data) - r := protocol.NewRequest("POST", "/foo", mr) - r.Header.Set("Content-Type", "application/json; charset=utf-8") - r.SetHeader("d", "ddd") - r.Header.SetContentLength(len(data)) - - var req Test - err := BindAndValidate(r, &req, nil) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - assert.DeepEqual(t, "aaa", req.A) - assert.DeepEqual(t, 2, len(req.B)) - assert.DeepEqual(t, "ccc", req.C) - // NOTE: The default does not support string to go int conversion in json. - // You can add "string" tags or use other json unmarshal libraries that support this feature - assert.DeepEqual(t, 100, req.D) - - req = Test{} - UseGJSONUnmarshaler() - err = BindAndValidate(r, &req, nil) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - assert.DeepEqual(t, "aaa", req.A) - assert.DeepEqual(t, 2, len(req.B)) - assert.DeepEqual(t, "ccc", req.C) - // NOTE: The default does not support string to go int conversion in json. - // You can add "string" tags or use other json unmarshal libraries that support this feature - assert.DeepEqual(t, 100, req.D) -} - -// TestQueryParamInconsistency tests the Inconsistency for GetQuery(), the other unit test for GetFunc() in request.go are similar to it -func TestQueryParamInconsistency(t *testing.T) { - type QueryPara struct { - Para1 string `query:"para1"` - Para2 *string `query:"para2"` - } - - r := protocol.NewRequest("GET", "/foo", nil) - r.SetRequestURI("/foo/bar?para1=hertz¶2=binding") - - var req QueryPara - err := BindAndValidate(r, &req, nil) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - beforePara1 := deepCopyString(req.Para1) - beforePara2 := deepCopyString(*req.Para2) - r.URI().QueryArgs().Set("para1", "test") - r.URI().QueryArgs().Set("para2", "test") - afterPara1 := req.Para1 - afterPara2 := *req.Para2 - assert.DeepEqual(t, beforePara1, afterPara1) - assert.DeepEqual(t, beforePara2, afterPara2) -} - -func deepCopyString(str string) string { - tmp := make([]byte, len(str)) - copy(tmp, str) - c := string(tmp) - - return c -} - -func TestBindingFile(t *testing.T) { - type FileParas struct { - F *multipart.FileHeader `form:"F1"` - F1 multipart.FileHeader - Fs []multipart.FileHeader `form:"F1"` - Fs1 []*multipart.FileHeader `form:"F1"` - F2 *multipart.FileHeader `form:"F2"` - } - - s := `------WebKitFormBoundaryJwfATyF8tmxSJnLg -Content-Disposition: form-data; name="f" - -fff -------WebKitFormBoundaryJwfATyF8tmxSJnLg -Content-Disposition: form-data; name="F1"; filename="TODO1" -Content-Type: application/octet-stream - -- SessionClient with referer and cookies support. -- Client with requests' pipelining support. -- ProxyHandler similar to FSHandler. -- WebSockets. See https://tools.ietf.org/html/rfc6455 . -- HTTP/2.0. See https://tools.ietf.org/html/rfc7540 . -------WebKitFormBoundaryJwfATyF8tmxSJnLg -Content-Disposition: form-data; name="F1"; filename="TODO2" -Content-Type: application/octet-stream - -- SessionClient with referer and cookies support. -- Client with requests' pipelining support. -- ProxyHandler similar to FSHandler. -- WebSockets. See https://tools.ietf.org/html/rfc6455 . -- HTTP/2.0. See https://tools.ietf.org/html/rfc7540 . -------WebKitFormBoundaryJwfATyF8tmxSJnLg -Content-Disposition: form-data; name="F2"; filename="TODO3" -Content-Type: application/octet-stream - -- SessionClient with referer and cookies support. -- Client with requests' pipelining support. -- ProxyHandler similar to FSHandler. -- WebSockets. See https://tools.ietf.org/html/rfc6455 . -- HTTP/2.0. See https://tools.ietf.org/html/rfc7540 . - -------WebKitFormBoundaryJwfATyF8tmxSJnLg-- -tailfoobar` - - mr := bytes.NewBufferString(s) - r := protocol.NewRequest("POST", "/foo", mr) - r.SetRequestURI("/foo/bar?a=aaa&b=b1&b=b2&c&i=19") - r.SetHeader("d", "ddd") - r.Header.SetContentLength(len(s)) - r.Header.SetContentTypeBytes([]byte("multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg")) - - var req FileParas - err := BindAndValidate(r, &req, nil) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - assert.DeepEqual(t, "TODO1", req.F.Filename) - assert.DeepEqual(t, "TODO1", req.F1.Filename) - assert.DeepEqual(t, 2, len(req.Fs)) - assert.DeepEqual(t, 2, len(req.Fs1)) - assert.DeepEqual(t, "TODO3", req.F2.Filename) -} - -type BindError struct { - ErrType, FailField, Msg string -} - -// Error implements error interface. -func (e *BindError) Error() string { - if e.Msg != "" { - return e.ErrType + ": expr_path=" + e.FailField + ", cause=" + e.Msg - } - return e.ErrType + ": expr_path=" + e.FailField + ", cause=invalid" -} - -type ValidateError struct { - ErrType, FailField, Msg string -} - -// Error implements error interface. -func (e *ValidateError) Error() string { - if e.Msg != "" { - return e.ErrType + ": expr_path=" + e.FailField + ", cause=" + e.Msg - } - return e.ErrType + ": expr_path=" + e.FailField + ", cause=invalid" -} - -func TestSetErrorFactory(t *testing.T) { - type TestBind struct { - A string `query:"a,required"` - } - - r := protocol.NewRequest("GET", "/foo", nil) - r.SetRequestURI("/foo/bar?b=20") - - CustomBindErrFunc := func(failField, msg string) error { - err := BindError{ - ErrType: "bindErr", - FailField: "[bindFailField]: " + failField, - Msg: "[bindErrMsg]: " + msg, - } - - return &err - } - - CustomValidateErrFunc := func(failField, msg string) error { - err := ValidateError{ - ErrType: "validateErr", - FailField: "[validateFailField]: " + failField, - Msg: "[validateErrMsg]: " + msg, - } - - return &err - } - - SetErrorFactory(CustomBindErrFunc, CustomValidateErrFunc) - - var req TestBind - err := Bind(r, &req, nil) - if err == nil { - t.Fatalf("unexpected nil, expected an error") - } - assert.DeepEqual(t, "bindErr: expr_path=[bindFailField]: A, cause=[bindErrMsg]: missing required parameter", err.Error()) - - type TestValidate struct { - B int `query:"b" vd:"$>100"` - } - - var reqValidate TestValidate - err = Bind(r, &reqValidate, nil) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - err = Validate(&reqValidate) - if err == nil { - t.Fatalf("unexpected nil, expected an error") - } - assert.DeepEqual(t, "validateErr: expr_path=[validateFailField]: B, cause=[validateErrMsg]: ", err.Error()) -} - -func TestMustRegTypeUnmarshal(t *testing.T) { - type Nested struct { - B string - C string - } - - type TestBind struct { - A Nested `query:"a,required"` - } - - r := protocol.NewRequest("GET", "/foo", nil) - r.SetRequestURI("/foo/bar?a=hertzbinding") - - MustRegTypeUnmarshal(reflect.TypeOf(Nested{}), func(v string, emptyAsZero bool) (reflect.Value, error) { - if v == "" && emptyAsZero { - return reflect.ValueOf(Nested{}), nil - } - val := Nested{ - B: v[:5], - C: v[5:], - } - return reflect.ValueOf(val), nil - }) - - var req TestBind - err := Bind(r, &req, nil) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - assert.DeepEqual(t, "hertz", req.A.B) - assert.DeepEqual(t, "binding", req.A.C) -} - -func TestMustRegValidateFunc(t *testing.T) { - type TestValidate struct { - A string `query:"a" vd:"test($)"` - } - - r := protocol.NewRequest("GET", "/foo", nil) - r.SetRequestURI("/foo/bar?a=123") - - MustRegValidateFunc("test", func(args ...interface{}) error { - if len(args) != 1 { - return fmt.Errorf("the args must be one") - } - s, _ := args[0].(string) - if s == "123" { - return fmt.Errorf("the args can not be 123") - } - return nil - }) - - var req TestValidate - err := Bind(r, &req, nil) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - err = Validate(&req) - if err == nil { - t.Fatalf("unexpected nil, expected an error") - } -} - -func TestQueryAlias(t *testing.T) { - type MyInt int - type MyString string - type MyIntSlice []int - type MyStringSlice []string - type Test struct { - A []MyInt `query:"a"` - B MyIntSlice `query:"b"` - C MyString `query:"c"` - D MyStringSlice `query:"d"` - } - - r := protocol.NewRequest("GET", "/foo", nil) - r.SetRequestURI("/foo/bar?a=1&a=2&b=2&b=3&c=string1&d=string2&d=string3") - - var req Test - err := Bind(r, &req, nil) - if err != nil { - t.Fatalf("unexpected error: %v", err) - return - } - assert.DeepEqual(t, 2, len(req.A)) - assert.DeepEqual(t, 1, int(req.A[0])) - assert.DeepEqual(t, 2, int(req.A[1])) - assert.DeepEqual(t, 2, len(req.B)) - assert.DeepEqual(t, 2, req.B[0]) - assert.DeepEqual(t, 3, req.B[1]) - assert.DeepEqual(t, "string1", string(req.C)) - assert.DeepEqual(t, 2, len(req.D)) - assert.DeepEqual(t, "string2", req.D[0]) - assert.DeepEqual(t, "string3", req.D[1]) -} diff --git a/pkg/app/server/binding/customized_type_decoder.go b/pkg/app/server/binding/customized_type_decoder.go new file mode 100644 index 000000000..87379c644 --- /dev/null +++ b/pkg/app/server/binding/customized_type_decoder.go @@ -0,0 +1,77 @@ +/* + * 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 ( + "reflect" + + "github.com/cloudwego/hertz/pkg/protocol" +) + +type customizedFieldTextDecoder struct { + fieldInfo +} + +func (d *customizedFieldTextDecoder) Decode(req *protocol.Request, params PathParams, reqValue reflect.Value) error { + var err error + v := reflect.New(d.fieldType) + decoder := v.Interface().(CustomizedFieldDecoder) + + if err = decoder.CustomizedFieldDecode(req, params); err != nil { + return err + } + + reqValue = GetFieldValue(reqValue, d.parentIndex) + field := reqValue.Field(d.index) + if field.Kind() == reflect.Ptr { + t := field.Type() + var ptrDepth int + for t.Kind() == reflect.Ptr { + t = t.Elem() + ptrDepth++ + } + field.Set(ReferenceValue(v.Elem(), ptrDepth)) + return nil + } + + field.Set(v) + return nil +} diff --git a/pkg/app/server/binding_v2/decoder.go b/pkg/app/server/binding/decoder.go similarity index 56% rename from pkg/app/server/binding_v2/decoder.go rename to pkg/app/server/binding/decoder.go index 8fc907ea3..f8bb12246 100644 --- a/pkg/app/server/binding_v2/decoder.go +++ b/pkg/app/server/binding/decoder.go @@ -1,4 +1,44 @@ -package binding_v2 +/* + * 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 ( "fmt" @@ -24,7 +64,7 @@ func getReqDecoder(rt reflect.Type) (Decoder, error) { el := rt.Elem() if el.Kind() != reflect.Struct { - return nil, fmt.Errorf("unsupport \"%s\" type binding", el.String()) + return nil, fmt.Errorf("unsupported \"%s\" type binding", el.String()) } for i := 0; i < el.NumField(); i++ { @@ -56,7 +96,6 @@ func getReqDecoder(rt reflect.Type) (Decoder, error) { } func getFieldDecoder(field reflect.StructField, index int, parentIdx []int) ([]decoder, error) { - // 去掉每一个filed的指针,使其指向最终内容 for field.Type.Kind() == reflect.Ptr { field.Type = field.Type.Elem() } @@ -72,7 +111,6 @@ func getFieldDecoder(field reflect.StructField, index int, parentIdx []int) ([]d } fieldTagInfos := lookupFieldTags(field) - // todo: 没有 tag 也不直接返回 if len(fieldTagInfos) == 0 { fieldTagInfos = getDefaultFieldTags(field) } @@ -81,12 +119,10 @@ func getFieldDecoder(field reflect.StructField, index int, parentIdx []int) ([]d return getSliceFieldDecoder(field, index, fieldTagInfos, parentIdx) } - // todo: reflect Map if field.Type.Kind() == reflect.Map { return getMapTypeTextDecoder(field, index, fieldTagInfos, parentIdx) } - // 递归每一个 struct if field.Type.Kind() == reflect.Struct { var decoders []decoder el := field.Type @@ -96,7 +132,6 @@ func getFieldDecoder(field reflect.StructField, index int, parentIdx []int) ([]d // ignore unexported field continue } - // todo: 优化一下? idxes := append(parentIdx, index) var idxes []int if len(parentIdx) > 0 { idxes = append(idxes, parentIdx...) diff --git a/pkg/app/server/binding/default_binder.go b/pkg/app/server/binding/default_binder.go new file mode 100644 index 000000000..18b5de06e --- /dev/null +++ b/pkg/app/server/binding/default_binder.go @@ -0,0 +1,112 @@ +/* + * 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 ( + "fmt" + "reflect" + "sync" + + "github.com/cloudwego/hertz/internal/bytesconv" + "github.com/cloudwego/hertz/pkg/protocol" + "google.golang.org/protobuf/proto" +) + +type Bind struct { + decoderCache sync.Map +} + +func (b *Bind) Name() string { + return "hertz" +} + +func (b *Bind) Bind(req *protocol.Request, params PathParams, v interface{}) error { + err := b.preBindBody(req, v) + if err != nil { + return err + } + rv, typeID := valueAndTypeID(v) + if rv.Kind() != reflect.Pointer || rv.IsNil() { + return fmt.Errorf("receiver must be a non-nil pointer") + } + if rv.Elem().Kind() == reflect.Map { + return nil + } + cached, ok := b.decoderCache.Load(typeID) + if ok { + // cached decoder, fast path + decoder := cached.(Decoder) + return decoder(req, params, rv.Elem()) + } + + decoder, err := getReqDecoder(rv.Type()) + if err != nil { + return err + } + + b.decoderCache.Store(typeID, decoder) + return decoder(req, params, rv.Elem()) +} + +var ( + jsonContentTypeBytes = "application/json; charset=utf-8" + protobufContentType = "application/x-protobuf" +) + +// best effort binding +func (b *Bind) preBindBody(req *protocol.Request, v interface{}) error { + if req.Header.ContentLength() <= 0 { + return nil + } + switch bytesconv.B2s(req.Header.ContentType()) { + case jsonContentTypeBytes: + // todo: Aligning the gin, add "EnableDecoderUseNumber"/"EnableDecoderDisallowUnknownFields" interface + return jsonUnmarshalFunc(req.Body(), v) + case protobufContentType: + msg, ok := v.(proto.Message) + if !ok { + return fmt.Errorf("%s can not implement 'proto.Message'", v) + } + return proto.Unmarshal(req.Body(), msg) + default: + return nil + } +} diff --git a/pkg/app/server/binding/default_validator.go b/pkg/app/server/binding/default_validator.go new file mode 100644 index 000000000..0ff3cd3d6 --- /dev/null +++ b/pkg/app/server/binding/default_validator.go @@ -0,0 +1,94 @@ +/* + * 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. + * The MIT License (MIT) + * + * Copyright (c) 2014 Manuel Martínez-Almeida + * + * 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 ( + "reflect" + "sync" + + "github.com/go-playground/validator/v10" +) + +var _ StructValidator = (*defaultValidator)(nil) + +type defaultValidator struct { + once sync.Once + validate *validator.Validate +} + +// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type. +func (v *defaultValidator) ValidateStruct(obj interface{}) error { + if obj == nil { + return nil + } + + value := reflect.ValueOf(obj) + switch value.Kind() { + case reflect.Ptr: + return v.ValidateStruct(value.Elem().Interface()) + case reflect.Struct: + return v.validateStruct(obj) + default: + return nil + } +} + +// validateStruct receives struct type +func (v *defaultValidator) validateStruct(obj interface{}) error { + v.lazyinit() + return v.validate.Struct(obj) +} + +func (v *defaultValidator) lazyinit() { + v.once.Do(func() { + v.validate = validator.New() + v.validate.SetTagName("validate") + }) +} + +// Engine returns the underlying validator engine which powers the default +// Validator instance. This is useful if you want to register custom validations +// or struct level validations. See validator GoDoc for more info - +// https://pkg.go.dev/github.com/go-playground/validator/v10 +func (v *defaultValidator) Engine() interface{} { + v.lazyinit() + return v.validate +} diff --git a/pkg/app/server/binding_v2/getter.go b/pkg/app/server/binding/getter.go similarity index 52% rename from pkg/app/server/binding_v2/getter.go rename to pkg/app/server/binding/getter.go index 895d1aa61..014aae323 100644 --- a/pkg/app/server/binding_v2/getter.go +++ b/pkg/app/server/binding/getter.go @@ -1,15 +1,52 @@ -package binding_v2 +/* + * 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 ( "github.com/cloudwego/hertz/internal/bytesconv" "github.com/cloudwego/hertz/pkg/protocol" ) -// todo: 优化,对于非数组类型的解析,要不要再提供一个不返回 []string 的 - type getter func(req *protocol.Request, params PathParams, key string, defaultValue ...string) (ret []string) -// todo string 强转优化 func PathParam(req *protocol.Request, params PathParams, key string, defaultValue ...string) (ret []string) { var value string if params != nil { @@ -26,18 +63,37 @@ func PathParam(req *protocol.Request, params PathParams, key string, defaultValu return } -// todo 区分postform和multipartform +// todo: Optimize 'postform' and 'multipart-form' func Form(req *protocol.Request, params PathParams, key string, defaultValue ...string) (ret []string) { req.URI().QueryArgs().VisitAll(func(queryKey, value []byte) { if bytesconv.B2s(queryKey) == key { ret = append(ret, string(value)) } }) + if len(ret) > 0 { + return + } + req.PostArgs().VisitAll(func(formKey, value []byte) { if bytesconv.B2s(formKey) == key { ret = append(ret, string(value)) } }) + if len(ret) > 0 { + return + } + + mf, err := req.MultipartForm() + if err == nil && mf.Value != nil { + for k, v := range mf.Value { + if k == key { + ret = append(ret, v[0]) + } + } + } + if len(ret) > 0 { + return + } if len(ret) == 0 && len(defaultValue) != 0 { ret = append(ret, defaultValue...) diff --git a/pkg/app/server/binding/json.go b/pkg/app/server/binding/json.go new file mode 100644 index 000000000..24407fb58 --- /dev/null +++ b/pkg/app/server/binding/json.go @@ -0,0 +1,65 @@ +/* + * 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_v2/map_type_decoder.go b/pkg/app/server/binding/map_type_decoder.go similarity index 55% rename from pkg/app/server/binding_v2/map_type_decoder.go rename to pkg/app/server/binding/map_type_decoder.go index bfdc993c1..2b9966f58 100644 --- a/pkg/app/server/binding_v2/map_type_decoder.go +++ b/pkg/app/server/binding/map_type_decoder.go @@ -1,4 +1,44 @@ -package binding_v2 +/* + * 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 ( "fmt" @@ -16,7 +56,6 @@ type mapTypeFieldTextDecoder struct { func (d *mapTypeFieldTextDecoder) Decode(req *protocol.Request, params PathParams, reqValue reflect.Value) error { var text string var defaultValue string - // 最大努力交付,对齐 hertz 现有设计 for _, tagInfo := range d.tagInfos { if tagInfo.Key == jsonTag { continue @@ -27,7 +66,6 @@ func (d *mapTypeFieldTextDecoder) Decode(req *protocol.Request, params PathParam ret := tagInfo.Getter(req, params, tagInfo.Value) defaultValue = tagInfo.Default if len(ret) != 0 { - // 非数组/切片类型,只取第一个值作为值 text = ret[0] break } @@ -42,7 +80,6 @@ func (d *mapTypeFieldTextDecoder) Decode(req *protocol.Request, params PathParam reqValue = GetFieldValue(reqValue, d.parentIndex) field := reqValue.Field(d.index) if field.Kind() == reflect.Ptr { - // 如果是指针则新建一个reflect.Value,然后赋值给指针 t := field.Type() var ptrDepth int for t.Kind() == reflect.Ptr { diff --git a/pkg/app/server/binding/reflect.go b/pkg/app/server/binding/reflect.go new file mode 100644 index 000000000..3471dbceb --- /dev/null +++ b/pkg/app/server/binding/reflect.go @@ -0,0 +1,113 @@ +/* + * 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 ( + "reflect" + "unsafe" +) + +func valueAndTypeID(v interface{}) (reflect.Value, uintptr) { + header := (*emptyInterface)(unsafe.Pointer(&v)) + + rv := reflect.ValueOf(v) + return rv, header.typeID +} + +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 +} diff --git a/pkg/app/server/binding/request.go b/pkg/app/server/binding/request.go deleted file mode 100644 index e4d70ba0d..000000000 --- a/pkg/app/server/binding/request.go +++ /dev/null @@ -1,138 +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. - */ - -package binding - -import ( - "mime/multipart" - "net/http" - "net/url" - - "github.com/bytedance/go-tagexpr/v2/binding" - "github.com/cloudwego/hertz/internal/bytesconv" - "github.com/cloudwego/hertz/pkg/protocol" -) - -func wrapRequest(req *protocol.Request) binding.Request { - r := &bindRequest{ - req: req, - } - return r -} - -type bindRequest struct { - req *protocol.Request -} - -func (r *bindRequest) GetQuery() url.Values { - queryMap := make(url.Values) - r.req.URI().QueryArgs().VisitAll(func(key, value []byte) { - keyStr := string(key) - values := queryMap[keyStr] - values = append(values, string(value)) - queryMap[keyStr] = values - }) - - return queryMap -} - -func (r *bindRequest) GetPostForm() (url.Values, error) { - postMap := make(url.Values) - r.req.PostArgs().VisitAll(func(key, value []byte) { - keyStr := string(key) - values := postMap[keyStr] - values = append(values, string(value)) - postMap[keyStr] = values - }) - mf, err := r.req.MultipartForm() - if err == nil { - for k, v := range mf.Value { - if len(v) > 0 { - postMap[k] = v - } - } - } - - return postMap, nil -} - -func (r *bindRequest) GetForm() (url.Values, error) { - formMap := make(url.Values) - r.req.URI().QueryArgs().VisitAll(func(key, value []byte) { - keyStr := string(key) - values := formMap[keyStr] - values = append(values, string(value)) - formMap[keyStr] = values - }) - r.req.PostArgs().VisitAll(func(key, value []byte) { - keyStr := string(key) - values := formMap[keyStr] - values = append(values, string(value)) - formMap[keyStr] = values - }) - - return formMap, nil -} - -func (r *bindRequest) GetCookies() []*http.Cookie { - var cookies []*http.Cookie - r.req.Header.VisitAllCookie(func(key, value []byte) { - cookies = append(cookies, &http.Cookie{ - Name: string(key), - Value: string(value), - }) - }) - - return cookies -} - -func (r *bindRequest) GetHeader() http.Header { - header := make(http.Header) - r.req.Header.VisitAll(func(key, value []byte) { - keyStr := string(key) - values := header[keyStr] - values = append(values, string(value)) - header[keyStr] = values - }) - - return header -} - -func (r *bindRequest) GetMethod() string { - return bytesconv.B2s(r.req.Method()) -} - -func (r *bindRequest) GetContentType() string { - return bytesconv.B2s(r.req.Header.ContentType()) -} - -func (r *bindRequest) GetBody() ([]byte, error) { - return r.req.Body(), nil -} - -func (r *bindRequest) GetFileHeaders() (map[string][]*multipart.FileHeader, error) { - files := make(map[string][]*multipart.FileHeader) - mf, err := r.req.MultipartForm() - if err == nil { - for k, v := range mf.File { - if len(v) > 0 { - files[k] = v - } - } - } - - return files, nil -} diff --git a/pkg/app/server/binding_v2/slice_type_decoder.go b/pkg/app/server/binding/slice_type_decoder.go similarity index 61% rename from pkg/app/server/binding_v2/slice_type_decoder.go rename to pkg/app/server/binding/slice_type_decoder.go index 74ac884c2..c2ced705d 100644 --- a/pkg/app/server/binding_v2/slice_type_decoder.go +++ b/pkg/app/server/binding/slice_type_decoder.go @@ -1,11 +1,51 @@ -package binding_v2 +/* + * 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 ( "fmt" "reflect" "github.com/cloudwego/hertz/internal/bytesconv" - "github.com/cloudwego/hertz/pkg/app/server/binding_v2/text_decoder" + "github.com/cloudwego/hertz/pkg/app/server/binding/text_decoder" "github.com/cloudwego/hertz/pkg/common/utils" "github.com/cloudwego/hertz/pkg/protocol" ) @@ -26,7 +66,7 @@ func (d *sliceTypeFieldTextDecoder) Decode(req *protocol.Request, params PathPar tagInfo.Value = utils.GetNormalizeHeaderKey(tagInfo.Value, req.Header.IsDisableNormalizing()) } texts = tagInfo.Getter(req, params, tagInfo.Value) - // todo: 数组默认值 + // todo: array/slice default value defaultValue = tagInfo.Default if len(texts) != 0 { break @@ -74,8 +114,6 @@ func (d *sliceTypeFieldTextDecoder) Decode(req *protocol.Request, params PathPar return nil } -// 数组/切片类型的decoder, -// 对于map和struct类型的数组元素直接使用unmarshal,不做嵌套处理 func getSliceFieldDecoder(field reflect.StructField, index int, tagInfos []TagInfo, parentIdx []int) ([]decoder, 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()) @@ -125,7 +163,7 @@ func getSliceFieldDecoder(field reflect.StructField, index int, tagInfos []TagIn func stringToValue(elemType reflect.Type, text string) (v reflect.Value, err error) { v = reflect.New(elemType).Elem() - // todo:自定义类型解析 + // todo: customized type binding switch elemType.Kind() { case reflect.Struct: @@ -137,7 +175,7 @@ func stringToValue(elemType reflect.Type, text string) (v reflect.Value, err err default: decoder, err := text_decoder.SelectTextDecoder(elemType) if err != nil { - return reflect.Value{}, fmt.Errorf("unsupport type %s for slice/array", elemType.String()) + return reflect.Value{}, fmt.Errorf("unsupported type %s for slice/array", elemType.String()) } err = decoder.UnmarshalString(text, v) if err != nil { @@ -145,5 +183,5 @@ func stringToValue(elemType reflect.Type, text string) (v reflect.Value, err err } } - return v, nil + return v, err } diff --git a/pkg/app/server/binding_v2/tag.go b/pkg/app/server/binding/tag.go similarity index 74% rename from pkg/app/server/binding_v2/tag.go rename to pkg/app/server/binding/tag.go index 90e625abd..6d5d88b0c 100644 --- a/pkg/app/server/binding_v2/tag.go +++ b/pkg/app/server/binding/tag.go @@ -1,4 +1,20 @@ -package binding_v2 +/* + * 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. + */ + +package binding import ( "reflect" diff --git a/pkg/app/server/binding/text_decoder/bool.go b/pkg/app/server/binding/text_decoder/bool.go new file mode 100644 index 000000000..5ae167296 --- /dev/null +++ b/pkg/app/server/binding/text_decoder/bool.go @@ -0,0 +1,57 @@ +/* + * 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 new file mode 100644 index 000000000..f44a1c76d --- /dev/null +++ b/pkg/app/server/binding/text_decoder/float.go @@ -0,0 +1,59 @@ +/* + * 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 new file mode 100644 index 000000000..1594e2016 --- /dev/null +++ b/pkg/app/server/binding/text_decoder/int.go @@ -0,0 +1,59 @@ +/* + * 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 new file mode 100644 index 000000000..46917469f --- /dev/null +++ b/pkg/app/server/binding/text_decoder/string.go @@ -0,0 +1,50 @@ +/* + * 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 +} diff --git a/pkg/app/server/binding/text_decoder/text_decoder.go b/pkg/app/server/binding/text_decoder/text_decoder.go new file mode 100644 index 000000000..08659aede --- /dev/null +++ b/pkg/app/server/binding/text_decoder/text_decoder.go @@ -0,0 +1,92 @@ +/* + * 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 ( + "fmt" + "reflect" +) + +type TextDecoder interface { + UnmarshalString(s string, fieldValue reflect.Value) error +} + +// var textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() + +func SelectTextDecoder(rt reflect.Type) (TextDecoder, error) { + // todo: encoding.TextUnmarshaler + //if reflect.PtrTo(rt).Implements(textUnmarshalerType) { + // return &textUnmarshalEncoder{fieldType: rt}, nil + //} + + switch rt.Kind() { + case reflect.Bool: + return &boolDecoder{}, nil + case reflect.Uint8: + return &uintDecoder{bitSize: 8}, nil + case reflect.Uint16: + return &uintDecoder{bitSize: 16}, nil + case reflect.Uint32: + return &uintDecoder{bitSize: 32}, nil + case reflect.Uint64: + return &uintDecoder{bitSize: 64}, nil + case reflect.Uint: + return &uintDecoder{}, nil + case reflect.Int8: + return &intDecoder{bitSize: 8}, nil + case reflect.Int16: + return &intDecoder{bitSize: 16}, nil + case reflect.Int32: + return &intDecoder{bitSize: 32}, nil + case reflect.Int64: + return &intDecoder{bitSize: 64}, nil + case reflect.Int: + return &intDecoder{}, nil + case reflect.String: + return &stringDecoder{}, nil + case reflect.Float32: + return &floatDecoder{bitSize: 32}, nil + case reflect.Float64: + return &floatDecoder{bitSize: 64}, nil + } + + return nil, fmt.Errorf("unsupported type " + rt.String()) +} diff --git a/pkg/app/server/binding/text_decoder/unit.go b/pkg/app/server/binding/text_decoder/unit.go new file mode 100644 index 000000000..1c3703b1c --- /dev/null +++ b/pkg/app/server/binding/text_decoder/unit.go @@ -0,0 +1,59 @@ +/* + * 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 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/validator.go b/pkg/app/server/binding/validator.go new file mode 100644 index 000000000..3332752a8 --- /dev/null +++ b/pkg/app/server/binding/validator.go @@ -0,0 +1,64 @@ +/* + * 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. + * The MIT License (MIT) + * + * Copyright (c) 2014 Manuel Martínez-Almeida + * + * 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 + +// StructValidator is the minimal interface which needs to be implemented in +// order for it to be used as the validator engine for ensuring the correctness +// of the request. Hertz provides a default implementation for this using +// https://github.com/go-playground/validator/tree/v10.6.1. +type StructValidator interface { + // ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right. + // If the received type is a slice|array, the validation should be performed travel on every element. + // If the received type is not a struct or slice|array, any validation should be skipped and nil must be returned. + // If the received type is a struct or pointer to a struct, the validation should be performed. + // If the struct is not valid or the validation itself fails, a descriptive error should be returned. + // Otherwise nil must be returned. + ValidateStruct(interface{}) error + + // Engine returns the underlying validator engine which powers the + // StructValidator implementation. + Engine() interface{} +} + +// DefaultValidator is the default validator which implements the StructValidator +// interface. It uses https://github.com/go-playground/validator/tree/v10.6.1 +// under the hood. +var DefaultValidator StructValidator = &defaultValidator{} diff --git a/pkg/app/server/binding/validator_test.go b/pkg/app/server/binding/validator_test.go new file mode 100644 index 000000000..3c7b5a292 --- /dev/null +++ b/pkg/app/server/binding/validator_test.go @@ -0,0 +1,47 @@ +/* + * 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. + */ + +package binding + +import ( + "fmt" +) + +func ExampleDefaultValidator_ValidateStruct() { + type User struct { + FirstName string `validate:"required"` + LastName string `validate:"required"` + Age uint8 `validate:"gte=0,lte=130"` + Email string `validate:"required,email"` + FavouriteColor string `validate:"iscolor"` + } + + user := &User{ + FirstName: "Hertz", + Age: 135, + Email: "hertz", + FavouriteColor: "sad", + } + err := DefaultValidator.ValidateStruct(user) + if err != nil { + fmt.Println(err) + } + // Output: + // Key: 'User.LastName' Error:Field validation for 'LastName' failed on the 'required' tag + // Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag + // Key: 'User.Email' Error:Field validation for 'Email' failed on the 'email' tag + // Key: 'User.FavouriteColor' Error:Field validation for 'FavouriteColor' failed on the 'iscolor' tag +} diff --git a/pkg/app/server/binding_v2/binder.go b/pkg/app/server/binding_v2/binder.go deleted file mode 100644 index 314e1d76e..000000000 --- a/pkg/app/server/binding_v2/binder.go +++ /dev/null @@ -1,17 +0,0 @@ -package binding_v2 - -import ( - "github.com/cloudwego/hertz/pkg/protocol" -) - -// PathParams parameter acquisition interface on the URL path -type PathParams interface { - Get(name string) (string, bool) -} - -type Binder interface { - Name() string - Bind(*protocol.Request, PathParams, interface{}) error -} - -var DefaultBinder Binder = &Bind{} diff --git a/pkg/app/server/binding_v2/customized_type_decoder.go b/pkg/app/server/binding_v2/customized_type_decoder.go deleted file mode 100644 index 302ea61e4..000000000 --- a/pkg/app/server/binding_v2/customized_type_decoder.go +++ /dev/null @@ -1,38 +0,0 @@ -package binding_v2 - -import ( - "reflect" - - "github.com/cloudwego/hertz/pkg/protocol" -) - -type customizedFieldTextDecoder struct { - fieldInfo -} - -func (d *customizedFieldTextDecoder) Decode(req *protocol.Request, params PathParams, reqValue reflect.Value) error { - var err error - v := reflect.New(d.fieldType) - decoder := v.Interface().(CustomizedFieldDecoder) - - if err = decoder.CustomizedFieldDecode(req, params); err != nil { - return err - } - - reqValue = GetFieldValue(reqValue, d.parentIndex) - field := reqValue.Field(d.index) - if field.Kind() == reflect.Ptr { - // 如果是指针则新建一个reflect.Value,然后赋值给指针 - t := field.Type() - var ptrDepth int - for t.Kind() == reflect.Ptr { - t = t.Elem() - ptrDepth++ - } - field.Set(ReferenceValue(v.Elem(), ptrDepth)) - return nil - } - - field.Set(v) - return nil -} diff --git a/pkg/app/server/binding_v2/default_binder.go b/pkg/app/server/binding_v2/default_binder.go deleted file mode 100644 index 9710249d9..000000000 --- a/pkg/app/server/binding_v2/default_binder.go +++ /dev/null @@ -1,73 +0,0 @@ -package binding_v2 - -import ( - "fmt" - "reflect" - "sync" - - "github.com/cloudwego/hertz/internal/bytesconv" - "github.com/cloudwego/hertz/pkg/protocol" - "google.golang.org/protobuf/proto" -) - -type Bind struct { - decoderCache sync.Map -} - -func (b *Bind) Name() string { - return "hertz" -} - -func (b *Bind) Bind(req *protocol.Request, params PathParams, v interface{}) error { - // todo: 先做 body unmarshal, 先尝试做 body 绑定,然后再尝试绑定其他内容 - err := b.preBindBody(req, v) - if err != nil { - return err - } - rv, typeID := valueAndTypeID(v) - if rv.Kind() != reflect.Pointer || rv.IsNil() { - return fmt.Errorf("receiver must be a non-nil pointer") - } - if rv.Elem().Kind() == reflect.Map { - return nil - } - cached, ok := b.decoderCache.Load(typeID) - if ok { - // cached decoder, fast path - decoder := cached.(Decoder) - return decoder(req, params, rv.Elem()) - } - - decoder, err := getReqDecoder(rv.Type()) - if err != nil { - return err - } - - b.decoderCache.Store(typeID, decoder) - return decoder(req, params, rv.Elem()) -} - -var ( - jsonContentTypeBytes = "application/json; charset=utf-8" - protobufContentType = "application/x-protobuf" -) - -// best effort binding -func (b *Bind) preBindBody(req *protocol.Request, v interface{}) error { - if req.Header.ContentLength() <= 0 { - return nil - } - switch bytesconv.B2s(req.Header.ContentType()) { - case jsonContentTypeBytes: - // todo: 对齐gin, 添加 "EnableDecoderUseNumber"/"EnableDecoderDisallowUnknownFields" 接口 - return jsonUnmarshalFunc(req.Body(), v) - case protobufContentType: - msg, ok := v.(proto.Message) - if !ok { - return fmt.Errorf("%s can not implement 'proto.Message'", v) - } - return proto.Unmarshal(req.Body(), msg) - default: - return nil - } -} diff --git a/pkg/app/server/binding_v2/default_validator.go b/pkg/app/server/binding_v2/default_validator.go deleted file mode 100644 index 988b05fc3..000000000 --- a/pkg/app/server/binding_v2/default_validator.go +++ /dev/null @@ -1,54 +0,0 @@ -package binding_v2 - -import ( - "reflect" - "sync" - - "github.com/go-playground/validator/v10" -) - -var _ StructValidator = (*defaultValidator)(nil) - -type defaultValidator struct { - once sync.Once - validate *validator.Validate -} - -// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type. -func (v *defaultValidator) ValidateStruct(obj interface{}) error { - if obj == nil { - return nil - } - - value := reflect.ValueOf(obj) - switch value.Kind() { - case reflect.Ptr: - return v.ValidateStruct(value.Elem().Interface()) - case reflect.Struct: - return v.validateStruct(obj) - default: - return nil - } -} - -// validateStruct receives struct type -func (v *defaultValidator) validateStruct(obj interface{}) error { - v.lazyinit() - return v.validate.Struct(obj) -} - -func (v *defaultValidator) lazyinit() { - v.once.Do(func() { - v.validate = validator.New() - v.validate.SetTagName("validate") - }) -} - -// Engine returns the underlying validator engine which powers the default -// Validator instance. This is useful if you want to register custom validations -// or struct level validations. See validator GoDoc for more info - -// https://pkg.go.dev/github.com/go-playground/validator/v10 -func (v *defaultValidator) Engine() interface{} { - v.lazyinit() - return v.validate -} diff --git a/pkg/app/server/binding_v2/json.go b/pkg/app/server/binding_v2/json.go deleted file mode 100644 index 049cc7456..000000000 --- a/pkg/app/server/binding_v2/json.go +++ /dev/null @@ -1,25 +0,0 @@ -package binding_v2 - -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_v2/reflect.go b/pkg/app/server/binding_v2/reflect.go deleted file mode 100644 index 79034aba8..000000000 --- a/pkg/app/server/binding_v2/reflect.go +++ /dev/null @@ -1,72 +0,0 @@ -package binding_v2 - -import ( - "reflect" - "unsafe" -) - -func valueAndTypeID(v interface{}) (reflect.Value, uintptr) { - header := (*emptyInterface)(unsafe.Pointer(&v)) - - rv := reflect.ValueOf(v) - return rv, header.typeID -} - -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) - } - - // 父 struct 有可能也是一个指针,所以需要再处理一次才能得到最终的父Value(非nil的reflect.Value) - for reqValue.Kind() == reflect.Ptr { - if reqValue.IsNil() { - nonNilVal, ptrDepth := GetNonNilReferenceValue(reqValue) - reqValue.Set(ReferenceValue(nonNilVal, ptrDepth)) - } - reqValue = reqValue.Elem() - } - - return reqValue -} diff --git a/pkg/app/server/binding_v2/text_decoder/bool.go b/pkg/app/server/binding_v2/text_decoder/bool.go deleted file mode 100644 index b669f5f9a..000000000 --- a/pkg/app/server/binding_v2/text_decoder/bool.go +++ /dev/null @@ -1,17 +0,0 @@ -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_v2/text_decoder/float.go b/pkg/app/server/binding_v2/text_decoder/float.go deleted file mode 100644 index 395526153..000000000 --- a/pkg/app/server/binding_v2/text_decoder/float.go +++ /dev/null @@ -1,19 +0,0 @@ -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_v2/text_decoder/int.go b/pkg/app/server/binding_v2/text_decoder/int.go deleted file mode 100644 index 13a26e644..000000000 --- a/pkg/app/server/binding_v2/text_decoder/int.go +++ /dev/null @@ -1,19 +0,0 @@ -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_v2/text_decoder/string.go b/pkg/app/server/binding_v2/text_decoder/string.go deleted file mode 100644 index 6290a4c31..000000000 --- a/pkg/app/server/binding_v2/text_decoder/string.go +++ /dev/null @@ -1,11 +0,0 @@ -package text_decoder - -import "reflect" - -type stringDecoder struct{} - -func (d *stringDecoder) UnmarshalString(s string, fieldValue reflect.Value) error { - // todo: 优化一下 - fieldValue.SetString(s) - return nil -} diff --git a/pkg/app/server/binding_v2/text_decoder/text_decoder.go b/pkg/app/server/binding_v2/text_decoder/text_decoder.go deleted file mode 100644 index 934b9a9c0..000000000 --- a/pkg/app/server/binding_v2/text_decoder/text_decoder.go +++ /dev/null @@ -1,52 +0,0 @@ -package text_decoder - -import ( - "fmt" - "reflect" -) - -type TextDecoder interface { - UnmarshalString(s string, fieldValue reflect.Value) error -} - -// var textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() - -func SelectTextDecoder(rt reflect.Type) (TextDecoder, error) { - // todo: encoding.TextUnmarshaler - //if reflect.PtrTo(rt).Implements(textUnmarshalerType) { - // return &textUnmarshalEncoder{fieldType: rt}, nil - //} - - switch rt.Kind() { - case reflect.Bool: - return &boolDecoder{}, nil - case reflect.Uint8: - return &uintDecoder{bitSize: 8}, nil - case reflect.Uint16: - return &uintDecoder{bitSize: 16}, nil - case reflect.Uint32: - return &uintDecoder{bitSize: 32}, nil - case reflect.Uint64: - return &uintDecoder{bitSize: 64}, nil - case reflect.Uint: - return &uintDecoder{}, nil - case reflect.Int8: - return &intDecoder{bitSize: 8}, nil - case reflect.Int16: - return &intDecoder{bitSize: 16}, nil - case reflect.Int32: - return &intDecoder{bitSize: 32}, nil - case reflect.Int64: - return &intDecoder{bitSize: 64}, nil - case reflect.Int: - return &intDecoder{}, nil - case reflect.String: - return &stringDecoder{}, nil - case reflect.Float32: - return &floatDecoder{bitSize: 32}, nil - case reflect.Float64: - return &floatDecoder{bitSize: 64}, nil - } - - return nil, fmt.Errorf("unsupported type " + rt.String()) -} diff --git a/pkg/app/server/binding_v2/text_decoder/unit.go b/pkg/app/server/binding_v2/text_decoder/unit.go deleted file mode 100644 index cb766964a..000000000 --- a/pkg/app/server/binding_v2/text_decoder/unit.go +++ /dev/null @@ -1,19 +0,0 @@ -package text_decoder - -import ( - "reflect" - "strconv" -) - -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_v2/validator.go b/pkg/app/server/binding_v2/validator.go deleted file mode 100644 index 1e418b70a..000000000 --- a/pkg/app/server/binding_v2/validator.go +++ /dev/null @@ -1,24 +0,0 @@ -package binding_v2 - -// StructValidator is the minimal interface which needs to be implemented in -// order for it to be used as the validator engine for ensuring the correctness -// of the request. Hertz provides a default implementation for this using -// https://github.com/go-playground/validator/tree/v10.6.1. -type StructValidator interface { - // ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right. - // If the received type is a slice|array, the validation should be performed travel on every element. - // If the received type is not a struct or slice|array, any validation should be skipped and nil must be returned. - // If the received type is a struct or pointer to a struct, the validation should be performed. - // If the struct is not valid or the validation itself fails, a descriptive error should be returned. - // Otherwise nil must be returned. - ValidateStruct(interface{}) error - - // Engine returns the underlying validator engine which powers the - // StructValidator implementation. - Engine() interface{} -} - -// DefaultValidator is the default validator which implements the StructValidator -// interface. It uses https://github.com/go-playground/validator/tree/v10.6.1 -// under the hood. -var DefaultValidator StructValidator = &defaultValidator{} diff --git a/pkg/app/server/binding_v2/validator_test.go b/pkg/app/server/binding_v2/validator_test.go deleted file mode 100644 index 2ec125810..000000000 --- a/pkg/app/server/binding_v2/validator_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package binding_v2 - -import ( - "fmt" -) - -func ExampleValidateStruct() { - type User struct { - FirstName string `validate:"required"` - LastName string `validate:"required"` - Age uint8 `validate:"gte=0,lte=130"` - Email string `validate:"required,email"` - FavouriteColor string `validate:"iscolor"` - } - - user := &User{ - FirstName: "Hertz", - Age: 135, - Email: "hertz", - FavouriteColor: "sad", - } - err := DefaultValidator.ValidateStruct(user) - if err != nil { - fmt.Println(err) - } - // Output: - //Key: 'User.LastName' Error:Field validation for 'LastName' failed on the 'required' tag - //Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag - //Key: 'User.Email' Error:Field validation for 'Email' failed on the 'email' tag - //Key: 'User.FavouriteColor' Error:Field validation for 'FavouriteColor' failed on the 'iscolor' tag -}