Skip to content

Commit 2b45081

Browse files
committed
feat: new http binding
1 parent 5c11ae1 commit 2b45081

18 files changed

+2317
-0
lines changed

http/binding/binding.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
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+
// Decoder decodes request data into a struct.
27+
// The Decode method binds values from RequestContext into the provided value v,
28+
// which must be a pointer to a struct.
29+
type Decoder interface {
30+
Decode(req RequestContext, v any) (bool, error)
31+
}
32+
33+
type fieldDecoder interface {
34+
Decode(req RequestContext, rv reflect.Value) (bool, error)
35+
GetFieldName() string
36+
}
37+
38+
type DecodeConfig struct {
39+
// JSONUnmarshalFunc is the function used for JSON unmarshaling
40+
// If nil, will use encoding/json.Unmarshal as default
41+
JSONUnmarshalFunc func(data []byte, v interface{}) error
42+
43+
// Tags specifies the tags to use for decoding in order of preference.
44+
// If not set (nil or empty), the default tags are used: path, form, query, cookie, header
45+
// If set (e.g., []string{"form", "query"}), only the specified tags are used in the given order.
46+
Tags []string
47+
}
48+
49+
func (c *DecodeConfig) getJSONUnmarshal() func(data []byte, v interface{}) error {
50+
if c.JSONUnmarshalFunc != nil {
51+
return c.JSONUnmarshalFunc
52+
}
53+
// Default to encoding/json
54+
return json.Unmarshal
55+
}
56+
57+
// NewDecoder creates a new Decoder for the given struct type.
58+
// The rt parameter must be a pointer to struct type (e.g., reflect.TypeOf((*MyStruct)(nil))).
59+
// The config parameter specifies decoding behavior (tags, JSON unmarshaler, etc.).
60+
// If config is nil, default configuration is used.
61+
//
62+
// Supported struct tags (in default priority order):
63+
// - path: binds from path parameters
64+
// - form: binds from POST form data, falls back to query parameters
65+
// - query: binds from URL query parameters
66+
// - cookie: binds from HTTP cookies
67+
// - header: binds from HTTP headers
68+
//
69+
// Returns an error if rt is not a pointer to struct type.
70+
func NewDecoder(rt reflect.Type, config *DecodeConfig) (Decoder, error) {
71+
if rt.Kind() != reflect.Pointer {
72+
return nil, errors.New("not pointer type")
73+
}
74+
rt = rt.Elem()
75+
if rt.Kind() != reflect.Struct {
76+
return nil, fmt.Errorf("unsupported %s type binding", rt)
77+
}
78+
if config == nil {
79+
config = &DecodeConfig{}
80+
}
81+
return newStructDecoder(rt, config)
82+
}
83+
84+
func getFieldDecoder(fi *fieldInfo) (fieldDecoder, error) {
85+
ft := fi.fieldType
86+
87+
fp := reflect.PointerTo(ft)
88+
// Priority: UnmarshalParam (custom) > TextUnmarshaler (standard) > base types
89+
if fp.Implements(paramUnmarshalerType) {
90+
return newUnmarshalParamDecoder(fi), nil
91+
}
92+
if fp.Implements(textUnmarshalerType) {
93+
return newTextUnmarshalerDecoder(fi), nil
94+
}
95+
96+
switch ft.Kind() {
97+
case reflect.Slice, reflect.Array:
98+
elemType := dereferenceType(ft.Elem())
99+
// Check if it's a file slice
100+
if elemType == fileBindingType {
101+
return newFileTypeSliceDecoder(fi), nil
102+
}
103+
104+
ep := reflect.PointerTo(elemType)
105+
// Check if element type implements UnmarshalParam
106+
if ep.Implements(paramUnmarshalerType) {
107+
return newUnmarshalParamSliceDecoder(fi), nil
108+
}
109+
// Check if element type implements TextUnmarshaler
110+
if ep.Implements(textUnmarshalerType) {
111+
return newTextUnmarshalerSliceDecoder(fi), nil
112+
}
113+
return newSliceDecoder(fi), nil
114+
115+
case reflect.Struct:
116+
if ft == fileBindingType {
117+
return newFileTypeDecoder(fi), nil
118+
}
119+
}
120+
return newBaseDecoder(fi), nil
121+
}
122+
123+
type textUnmarshaler interface {
124+
UnmarshalText(text []byte) error
125+
}
126+
127+
var textUnmarshalerType = reflect.TypeOf((*textUnmarshaler)(nil)).Elem()
128+
129+
type paramUnmarshaler interface {
130+
UnmarshalParam(param string) error
131+
}
132+
133+
var paramUnmarshalerType = reflect.TypeOf((*paramUnmarshaler)(nil)).Elem()

http/binding/binding_base.go

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
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+
v, 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+
s := b2s(v)
52+
if err := d.decodeValue(rv, s); err != nil {
53+
f.Reset()
54+
return false, fmt.Errorf("unable to decode '%s' as %s: %w", s, d.fieldType.String(), err)
55+
}
56+
return true, nil
57+
}
58+
59+
// use slice for better performance,
60+
var type2decoder = [...]func(rv reflect.Value, s string) error{
61+
reflect.Bool: decodeBool,
62+
reflect.Uint: decodeUint,
63+
reflect.Uint8: decodeUint8,
64+
reflect.Uint16: decodeUint16,
65+
reflect.Uint32: decodeUint32,
66+
reflect.Uint64: decodeUint64,
67+
reflect.Int: decodeInt,
68+
reflect.Int8: decodeInt8,
69+
reflect.Int16: decodeInt16,
70+
reflect.Int32: decodeInt32,
71+
reflect.Int64: decodeInt64,
72+
reflect.String: decodeString,
73+
reflect.Float32: decodeFloat32,
74+
reflect.Float64: decodeFloat64,
75+
}
76+
77+
func getBaseDecodeByKind(k reflect.Kind) (ret func(rv reflect.Value, s string) error) {
78+
if int(k) >= len(type2decoder) {
79+
return nil
80+
}
81+
return type2decoder[k]
82+
}
83+
84+
// decodeJSONValue is a method on baseDecoder that uses the configured JSON unmarshal function
85+
func (d *baseDecoder) decodeJSONValue(rv reflect.Value, s string) error {
86+
return d.jsonUnmarshal(s2b(s), rv.Addr().Interface())
87+
}
88+
89+
func decodeBool(rv reflect.Value, s string) error {
90+
v, err := strconv.ParseBool(s)
91+
if err == nil {
92+
*(*bool)(rvUnsafePointer(&rv)) = v
93+
}
94+
return err
95+
96+
}
97+
98+
func decodeUint(rv reflect.Value, s string) error {
99+
v, err := strconv.ParseUint(s, 10, 0)
100+
if err == nil {
101+
*(*uint)(rvUnsafePointer(&rv)) = uint(v)
102+
}
103+
return err
104+
105+
}
106+
107+
func decodeUint8(rv reflect.Value, s string) error {
108+
v, err := strconv.ParseUint(s, 10, 8)
109+
if err == nil {
110+
*(*uint8)(rvUnsafePointer(&rv)) = uint8(v)
111+
}
112+
return err
113+
114+
}
115+
116+
func decodeUint16(rv reflect.Value, s string) error {
117+
v, err := strconv.ParseUint(s, 10, 16)
118+
if err == nil {
119+
*(*uint16)(rvUnsafePointer(&rv)) = uint16(v)
120+
}
121+
return err
122+
}
123+
124+
func decodeUint32(rv reflect.Value, s string) error {
125+
v, err := strconv.ParseUint(s, 10, 32)
126+
if err == nil {
127+
*(*uint32)(rvUnsafePointer(&rv)) = uint32(v)
128+
}
129+
return err
130+
131+
}
132+
133+
func decodeUint64(rv reflect.Value, s string) error {
134+
v, err := strconv.ParseUint(s, 10, 64)
135+
if err == nil {
136+
*(*uint64)(rvUnsafePointer(&rv)) = v
137+
}
138+
return err
139+
140+
}
141+
142+
func decodeInt(rv reflect.Value, s string) error {
143+
v, err := strconv.Atoi(s)
144+
if err == nil {
145+
*(*int)(rvUnsafePointer(&rv)) = v
146+
}
147+
return err
148+
149+
}
150+
151+
func decodeInt8(rv reflect.Value, s string) error {
152+
v, err := strconv.ParseInt(s, 10, 8)
153+
if err == nil {
154+
*(*int8)(rvUnsafePointer(&rv)) = int8(v)
155+
}
156+
return err
157+
158+
}
159+
160+
func decodeInt16(rv reflect.Value, s string) error {
161+
v, err := strconv.ParseInt(s, 10, 16)
162+
if err == nil {
163+
*(*int16)(rvUnsafePointer(&rv)) = int16(v)
164+
}
165+
return err
166+
}
167+
168+
func decodeInt32(rv reflect.Value, s string) error {
169+
v, err := strconv.ParseInt(s, 10, 32)
170+
if err == nil {
171+
*(*int32)(rvUnsafePointer(&rv)) = int32(v)
172+
}
173+
return err
174+
}
175+
176+
func decodeInt64(rv reflect.Value, s string) error {
177+
v, err := strconv.ParseInt(s, 10, 64)
178+
if err == nil {
179+
*(*int64)(rvUnsafePointer(&rv)) = v
180+
}
181+
return err
182+
}
183+
184+
func decodeString(rv reflect.Value, s string) error {
185+
*(*string)(rvUnsafePointer(&rv)) = s
186+
return nil
187+
}
188+
189+
func decodeFloat32(rv reflect.Value, s string) error {
190+
v, err := strconv.ParseFloat(s, 32)
191+
if err == nil {
192+
*(*float32)(rvUnsafePointer(&rv)) = float32(v)
193+
}
194+
return err
195+
}
196+
197+
func decodeFloat64(rv reflect.Value, s string) error {
198+
v, err := strconv.ParseFloat(s, 64)
199+
if err == nil {
200+
*(*float64)(rvUnsafePointer(&rv)) = v
201+
}
202+
return err
203+
}

0 commit comments

Comments
 (0)