Skip to content

Commit 3023f55

Browse files
committed
feat: new http binding
1 parent 5c11ae1 commit 3023f55

19 files changed

+2438
-0
lines changed

http/binding/binding.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright 2025 CloudWeGo Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package binding
18+
19+
import (
20+
"encoding/json"
21+
"errors"
22+
"fmt"
23+
"reflect"
24+
)
25+
26+
var byteSliceType = reflect.TypeOf([]byte(nil))
27+
28+
// Decoder decodes request data into a struct.
29+
// The Decode method binds values from RequestContext into the provided value v,
30+
// which must be a pointer to a struct.
31+
type Decoder interface {
32+
Decode(req RequestContext, v any) (bool, error)
33+
}
34+
35+
type fieldDecoder interface {
36+
Decode(req RequestContext, rv reflect.Value) (bool, error)
37+
GetFieldName() string
38+
}
39+
40+
type DecodeConfig struct {
41+
// JSONUnmarshalFunc is the function used for JSON unmarshaling
42+
// If nil, will use encoding/json.Unmarshal as default
43+
JSONUnmarshalFunc func(data []byte, v interface{}) error
44+
45+
// Tags specifies the tags to use for decoding in order of preference.
46+
// If not set (nil or empty), the default tags are used: path, form, query, cookie, header
47+
// If set (e.g., []string{"form", "query"}), only the specified tags are used in the given order.
48+
Tags []string
49+
}
50+
51+
func (c *DecodeConfig) getJSONUnmarshal() func(data []byte, v interface{}) error {
52+
if c.JSONUnmarshalFunc != nil {
53+
return c.JSONUnmarshalFunc
54+
}
55+
// Default to encoding/json
56+
return json.Unmarshal
57+
}
58+
59+
// NewDecoder creates a new Decoder for the given struct type.
60+
// The rt parameter must be a pointer to struct type (e.g., reflect.TypeOf((*MyStruct)(nil))).
61+
// The config parameter specifies decoding behavior (tags, JSON unmarshaler, etc.).
62+
// If config is nil, default configuration is used.
63+
//
64+
// Supported struct tags (in default priority order):
65+
// - path: binds from path parameters
66+
// - form: binds from POST form data, falls back to query parameters
67+
// - query: binds from URL query parameters
68+
// - cookie: binds from HTTP cookies
69+
// - header: binds from HTTP headers
70+
//
71+
// Returns an error if rt is not a pointer to struct type.
72+
func NewDecoder(rt reflect.Type, config *DecodeConfig) (Decoder, error) {
73+
if rt.Kind() != reflect.Pointer {
74+
return nil, errors.New("not pointer type")
75+
}
76+
rt = rt.Elem()
77+
if rt.Kind() != reflect.Struct {
78+
return nil, fmt.Errorf("unsupported %s type binding", rt)
79+
}
80+
if config == nil {
81+
config = &DecodeConfig{}
82+
}
83+
return newStructDecoder(rt, config)
84+
}
85+
86+
func getFieldDecoder(fi *fieldInfo) (fieldDecoder, error) {
87+
ft := fi.fieldType
88+
89+
fp := reflect.PointerTo(ft)
90+
// Priority: UnmarshalParam (custom) > TextUnmarshaler (standard) > base types
91+
if fp.Implements(paramUnmarshalerType) {
92+
return newUnmarshalParamDecoder(fi), nil
93+
}
94+
if fp.Implements(textUnmarshalerType) {
95+
return newTextUnmarshalerDecoder(fi), nil
96+
}
97+
98+
switch ft.Kind() {
99+
case reflect.Slice, reflect.Array:
100+
elemType := dereferenceType(ft.Elem())
101+
// Check if it's a file slice
102+
if elemType == fileBindingType {
103+
return newFileTypeSliceDecoder(fi), nil
104+
}
105+
106+
ep := reflect.PointerTo(elemType)
107+
// Check if element type implements UnmarshalParam
108+
if ep.Implements(paramUnmarshalerType) {
109+
return newUnmarshalParamSliceDecoder(fi), nil
110+
}
111+
// Check if element type implements TextUnmarshaler
112+
if ep.Implements(textUnmarshalerType) {
113+
return newTextUnmarshalerSliceDecoder(fi), nil
114+
}
115+
return newSliceDecoder(fi), nil
116+
117+
case reflect.Struct:
118+
if ft == fileBindingType {
119+
return newFileTypeDecoder(fi), nil
120+
}
121+
}
122+
return newBaseDecoder(fi), nil
123+
}
124+
125+
type textUnmarshaler interface {
126+
UnmarshalText(text []byte) error
127+
}
128+
129+
var textUnmarshalerType = reflect.TypeOf((*textUnmarshaler)(nil)).Elem()
130+
131+
type paramUnmarshaler interface {
132+
UnmarshalParam(param string) error
133+
}
134+
135+
var paramUnmarshalerType = reflect.TypeOf((*paramUnmarshaler)(nil)).Elem()

http/binding/binding_base.go

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
/*
2+
* Copyright 2025 CloudWeGo Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package binding
18+
19+
import (
20+
"fmt"
21+
"reflect"
22+
"strconv"
23+
)
24+
25+
type baseDecoder struct {
26+
*fieldInfo
27+
28+
decodeValue func(rv reflect.Value, s string) error
29+
}
30+
31+
func newBaseDecoder(fi *fieldInfo) fieldDecoder {
32+
decoder := &baseDecoder{fieldInfo: fi}
33+
fn := getBaseDecodeByKind(fi.fieldType.Kind())
34+
if fn == nil {
35+
// Use method that has access to jsonUnmarshal
36+
fn = decoder.decodeJSONValue
37+
}
38+
decoder.decodeValue = fn
39+
return decoder
40+
}
41+
42+
func (d *baseDecoder) Decode(req RequestContext, rv reflect.Value) (bool, error) {
43+
s, ok, err := d.FetchBindValue(req)
44+
if !ok || err != nil {
45+
return false, err
46+
}
47+
48+
f := d.FieldSetter(rv)
49+
rv = f.Value()
50+
51+
if err := d.decodeValue(rv, s); err != nil {
52+
f.Reset()
53+
return false, fmt.Errorf("unable to decode '%s' as %s: %w", s, d.fieldType.String(), err)
54+
}
55+
return true, nil
56+
}
57+
58+
// use slice for better performance,
59+
var type2decoder = [...]func(rv reflect.Value, s string) error{
60+
reflect.Bool: decodeBool,
61+
reflect.Uint: decodeUint,
62+
reflect.Uint8: decodeUint8,
63+
reflect.Uint16: decodeUint16,
64+
reflect.Uint32: decodeUint32,
65+
reflect.Uint64: decodeUint64,
66+
reflect.Int: decodeInt,
67+
reflect.Int8: decodeInt8,
68+
reflect.Int16: decodeInt16,
69+
reflect.Int32: decodeInt32,
70+
reflect.Int64: decodeInt64,
71+
reflect.String: decodeString,
72+
reflect.Float32: decodeFloat32,
73+
reflect.Float64: decodeFloat64,
74+
}
75+
76+
func getBaseDecodeByKind(k reflect.Kind) (ret func(rv reflect.Value, s string) error) {
77+
if int(k) >= len(type2decoder) {
78+
return nil
79+
}
80+
return type2decoder[k]
81+
}
82+
83+
// decodeJSONValue is a method on baseDecoder that uses the configured JSON unmarshal function
84+
func (d *baseDecoder) decodeJSONValue(rv reflect.Value, s string) error {
85+
return d.jsonUnmarshal(s2b(s), rv.Addr().Interface())
86+
}
87+
88+
func decodeBool(rv reflect.Value, s string) error {
89+
v, err := strconv.ParseBool(s)
90+
if err == nil {
91+
*(*bool)(rvUnsafePointer(&rv)) = v
92+
}
93+
return err
94+
95+
}
96+
97+
func decodeUint(rv reflect.Value, s string) error {
98+
v, err := strconv.ParseUint(s, 10, 0)
99+
if err == nil {
100+
*(*uint)(rvUnsafePointer(&rv)) = uint(v)
101+
}
102+
return err
103+
104+
}
105+
106+
func decodeUint8(rv reflect.Value, s string) error {
107+
v, err := strconv.ParseUint(s, 10, 8)
108+
if err == nil {
109+
*(*uint8)(rvUnsafePointer(&rv)) = uint8(v)
110+
}
111+
return err
112+
113+
}
114+
115+
func decodeUint16(rv reflect.Value, s string) error {
116+
v, err := strconv.ParseUint(s, 10, 16)
117+
if err == nil {
118+
*(*uint16)(rvUnsafePointer(&rv)) = uint16(v)
119+
}
120+
return err
121+
}
122+
123+
func decodeUint32(rv reflect.Value, s string) error {
124+
v, err := strconv.ParseUint(s, 10, 32)
125+
if err == nil {
126+
*(*uint32)(rvUnsafePointer(&rv)) = uint32(v)
127+
}
128+
return err
129+
130+
}
131+
132+
func decodeUint64(rv reflect.Value, s string) error {
133+
v, err := strconv.ParseUint(s, 10, 64)
134+
if err == nil {
135+
*(*uint64)(rvUnsafePointer(&rv)) = v
136+
}
137+
return err
138+
139+
}
140+
141+
func decodeInt(rv reflect.Value, s string) error {
142+
v, err := strconv.Atoi(s)
143+
if err == nil {
144+
*(*int)(rvUnsafePointer(&rv)) = v
145+
}
146+
return err
147+
148+
}
149+
150+
func decodeInt8(rv reflect.Value, s string) error {
151+
v, err := strconv.ParseInt(s, 10, 8)
152+
if err == nil {
153+
*(*int8)(rvUnsafePointer(&rv)) = int8(v)
154+
}
155+
return err
156+
157+
}
158+
159+
func decodeInt16(rv reflect.Value, s string) error {
160+
v, err := strconv.ParseInt(s, 10, 16)
161+
if err == nil {
162+
*(*int16)(rvUnsafePointer(&rv)) = int16(v)
163+
}
164+
return err
165+
}
166+
167+
func decodeInt32(rv reflect.Value, s string) error {
168+
v, err := strconv.ParseInt(s, 10, 32)
169+
if err == nil {
170+
*(*int32)(rvUnsafePointer(&rv)) = int32(v)
171+
}
172+
return err
173+
}
174+
175+
func decodeInt64(rv reflect.Value, s string) error {
176+
v, err := strconv.ParseInt(s, 10, 64)
177+
if err == nil {
178+
*(*int64)(rvUnsafePointer(&rv)) = v
179+
}
180+
return err
181+
}
182+
183+
func decodeString(rv reflect.Value, s string) error {
184+
*(*string)(rvUnsafePointer(&rv)) = s
185+
return nil
186+
}
187+
188+
func decodeFloat32(rv reflect.Value, s string) error {
189+
v, err := strconv.ParseFloat(s, 32)
190+
if err == nil {
191+
*(*float32)(rvUnsafePointer(&rv)) = float32(v)
192+
}
193+
return err
194+
}
195+
196+
func decodeFloat64(rv reflect.Value, s string) error {
197+
v, err := strconv.ParseFloat(s, 64)
198+
if err == nil {
199+
*(*float64)(rvUnsafePointer(&rv)) = v
200+
}
201+
return err
202+
}

0 commit comments

Comments
 (0)