Skip to content

Commit 81aa400

Browse files
committed
feat: new http binding
1 parent 5c11ae1 commit 81aa400

18 files changed

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

0 commit comments

Comments
 (0)