forked from gofiber/fiber
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mapping.go
210 lines (178 loc) · 5.04 KB
/
mapping.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
package binder
import (
"reflect"
"strings"
"sync"
"github.com/gofiber/fiber/v3/internal/schema"
"github.com/gofiber/utils/v2"
"github.com/valyala/bytebufferpool"
)
// ParserConfig form decoder config for SetParserDecoder
type ParserConfig struct {
IgnoreUnknownKeys bool
SetAliasTag string
ParserType []ParserType
ZeroEmpty bool
}
// ParserType require two element, type and converter for register.
// Use ParserType with BodyParser for parsing custom type in form data.
type ParserType struct {
Customtype any
Converter func(string) reflect.Value
}
var (
// decoderPoolMap helps to improve binders
decoderPoolMap = map[string]*sync.Pool{}
// tags is used to classify parser's pool
tags = []string{HeaderBinder.Name(), RespHeaderBinder.Name(), CookieBinder.Name(), QueryBinder.Name(), FormBinder.Name(), URIBinder.Name()}
)
// SetParserDecoder allow globally change the option of form decoder, update decoderPool
func SetParserDecoder(parserConfig ParserConfig) {
for _, tag := range tags {
decoderPoolMap[tag] = &sync.Pool{New: func() any {
return decoderBuilder(parserConfig)
}}
}
}
func decoderBuilder(parserConfig ParserConfig) any {
decoder := schema.NewDecoder()
decoder.IgnoreUnknownKeys(parserConfig.IgnoreUnknownKeys)
if parserConfig.SetAliasTag != "" {
decoder.SetAliasTag(parserConfig.SetAliasTag)
}
for _, v := range parserConfig.ParserType {
decoder.RegisterConverter(reflect.ValueOf(v.Customtype).Interface(), v.Converter)
}
decoder.ZeroEmpty(parserConfig.ZeroEmpty)
return decoder
}
func init() {
for _, tag := range tags {
decoderPoolMap[tag] = &sync.Pool{New: func() any {
return decoderBuilder(ParserConfig{
IgnoreUnknownKeys: true,
ZeroEmpty: true,
})
}}
}
}
// parse data into the map or struct
func parse(aliasTag string, out any, data map[string][]string) error {
ptrVal := reflect.ValueOf(out)
// Get pointer value
if ptrVal.Kind() == reflect.Ptr {
ptrVal = ptrVal.Elem()
}
// Parse into the map
if ptrVal.Kind() == reflect.Map && ptrVal.Type().Key().Kind() == reflect.String {
return parseToMap(ptrVal.Interface(), data)
}
// Parse into the struct
return parseToStruct(aliasTag, out, data)
}
// Parse data into the struct with gorilla/schema
func parseToStruct(aliasTag string, out any, data map[string][]string) error {
// Get decoder from pool
schemaDecoder := decoderPoolMap[aliasTag].Get().(*schema.Decoder) //nolint:errcheck,forcetypeassert // not needed
defer decoderPoolMap[aliasTag].Put(schemaDecoder)
// Set alias tag
schemaDecoder.SetAliasTag(aliasTag)
return schemaDecoder.Decode(out, data)
}
// Parse data into the map
// thanks to https://github.com/gin-gonic/gin/blob/master/binding/binding.go
func parseToMap(ptr any, data map[string][]string) error {
elem := reflect.TypeOf(ptr).Elem()
// map[string][]string
if elem.Kind() == reflect.Slice {
newMap, ok := ptr.(map[string][]string)
if !ok {
return ErrMapNotConvertable
}
for k, v := range data {
newMap[k] = v
}
return nil
}
// map[string]string
newMap, ok := ptr.(map[string]string)
if !ok {
return ErrMapNotConvertable
}
for k, v := range data {
newMap[k] = v[len(v)-1]
}
return nil
}
func parseParamSquareBrackets(k string) (string, error) {
bb := bytebufferpool.Get()
defer bytebufferpool.Put(bb)
kbytes := []byte(k)
for i, b := range kbytes {
if b == '[' && kbytes[i+1] != ']' {
if err := bb.WriteByte('.'); err != nil {
return "", err //nolint:wrapcheck // unnecessary to wrap it
}
}
if b == '[' || b == ']' {
continue
}
if err := bb.WriteByte(b); err != nil {
return "", err //nolint:wrapcheck // unnecessary to wrap it
}
}
return bb.String(), nil
}
func equalFieldType(out any, kind reflect.Kind, key string) bool {
// Get type of interface
outTyp := reflect.TypeOf(out).Elem()
key = utils.ToLower(key)
// Support maps
if outTyp.Kind() == reflect.Map && outTyp.Key().Kind() == reflect.String {
return true
}
// Must be a struct to match a field
if outTyp.Kind() != reflect.Struct {
return false
}
// Copy interface to an value to be used
outVal := reflect.ValueOf(out).Elem()
// Loop over each field
for i := 0; i < outTyp.NumField(); i++ {
// Get field value data
structField := outVal.Field(i)
// Can this field be changed?
if !structField.CanSet() {
continue
}
// Get field key data
typeField := outTyp.Field(i)
// Get type of field key
structFieldKind := structField.Kind()
// Does the field type equals input?
if structFieldKind != kind {
continue
}
// Get tag from field if exist
inputFieldName := typeField.Tag.Get(QueryBinder.Name())
if inputFieldName == "" {
inputFieldName = typeField.Name
} else {
inputFieldName = strings.Split(inputFieldName, ",")[0]
}
// Compare field/tag with provided key
if utils.ToLower(inputFieldName) == key {
return true
}
}
return false
}
// Get content type from content type header
func FilterFlags(content string) string {
for i, char := range content {
if char == ' ' || char == ';' {
return content[:i]
}
}
return content
}