-
Notifications
You must be signed in to change notification settings - Fork 2
/
parse.go
188 lines (158 loc) · 4.21 KB
/
parse.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
// SPDX-FileCopyrightText: 2018-2024 caixw
//
// SPDX-License-Identifier: MIT
package query
import (
"encoding"
"net/url"
"reflect"
"strings"
"unicode"
"github.com/issue9/conv"
)
// Parse 将查询参数解析到 v
func Parse(queries url.Values, v any) map[string]error {
errors := make(map[string]error, 10)
ParseWithLog(queries, v, func(name string, err error) { errors[name] = err })
return errors
}
// ParseWithLog 将查询参数解析至 v 中
//
// 如果 queries 中的元素,实现了 [Unmarshaler] 或是 [encoding.TextUnmarshaler],
// 则会调用相应的接口解码。
//
// 如果有错误,则调用 log 方法进行处理,log 原型如下:
//
// func(name string, err error)
//
// 其中的 name 表示查询参数名称,err 表示解析该参数时的错误信息。
//
// v 只能是指针,如果是指针的批针,请注意接口的实现是否符合你的预期。
//
// NOTE: ParseWithLog 适合在已经有错误处理方式的代码中使用,比如库的作者。
// 一般情况下 [Parse] 会更佳合适。
func ParseWithLog(queries url.Values, v any, log func(string, error)) {
rval := reflect.ValueOf(v)
if rval.Kind() != reflect.Ptr {
panic("v 必须为指针")
}
for rval.Kind() == reflect.Ptr {
rval = rval.Elem()
}
// NOTE: 不应该由 Parse 实现对整个对象内容的检测,那应该是 v 的实现应当做的事。
parseField(queries, rval, log)
}
func parseField(vals url.Values, rval reflect.Value, log func(string, error)) {
rtype := rval.Type()
for i := 0; i < rtype.NumField(); i++ {
tf := rtype.Field(i)
if tf.Anonymous {
parseField(vals, rval.Field(i), log)
continue
}
vf := rval.Field(i)
switch tf.Type.Kind() {
case reflect.Slice:
parseSliceFieldValue(vals, log, tf, vf)
case reflect.Ptr, reflect.Chan, reflect.Func, reflect.Array, reflect.Complex128, reflect.Complex64:
// 这些类型的字段,直接忽略
default:
parseFieldValue(vals, log, tf, vf)
}
}
}
func parseFieldValue(vals url.Values, log func(string, error), tf reflect.StructField, vf reflect.Value) {
name, def := getQueryTag(tf)
if name == "" {
return
}
val := vals.Get(name)
if val == "" {
if vf.Interface() != reflect.Zero(tf.Type).Interface() {
return
}
val = def
}
if val == "" { // 依然是空值
return
}
unmarshal(name, vf.Addr(), val, log)
}
func parseSliceFieldValue(form url.Values, log func(string, error), tf reflect.StructField, vf reflect.Value) {
name, def := getQueryTag(tf)
if name == "" {
return
}
vals := make([]string, 0, len(form[name]))
for _, val := range form[name] {
if val == "" {
continue
}
vals = append(vals, val)
}
if len(vals) == 0 {
if vf.Len() > 0 { // 实例有默认值,则采用默认值
return
}
if def == "" {
return
}
vals = []string{def}
}
if len(vals) == 1 {
vals = strings.Split(vals[0], ",")
}
// 指定了参数,则舍弃 slice 中的旧值
vf.Set(vf.Slice(0, 0))
elemType := tf.Type.Elem()
for elemType.Kind() == reflect.Ptr {
elemType = elemType.Elem()
}
for _, v := range vals {
elem := reflect.New(elemType)
if !unmarshal(name, elem, v, log) {
return
}
vf.Set(reflect.Append(vf, elem.Elem()))
}
}
func unmarshal(name string, vf reflect.Value, val string, log func(string, error)) (ok bool) {
if q, ok := vf.Interface().(Unmarshaler); ok {
if err := q.UnmarshalQuery(val); err != nil {
log(name, err)
return false
}
} else if u, ok := vf.Interface().(encoding.TextUnmarshaler); ok {
if err := u.UnmarshalText([]byte(val)); err != nil {
log(name, err)
return false
}
} else if err := conv.Value(val, vf); err != nil {
log(name, err)
return false
}
return true
}
// 返回值中的 name 如果为空,表示忽略这个字段的内容。
func getQueryTag(field reflect.StructField) (name, def string) {
if field.Name != "" && unicode.IsLower(rune(field.Name[0])) {
return "", ""
}
tag := field.Tag.Get(Tag)
if tag == "-" {
return "", ""
}
tags := strings.SplitN(tag, ",", 2)
switch len(tags) {
case 0: // 都采用默认值
case 1:
name = strings.TrimSpace(tags[0])
case 2:
name = strings.TrimSpace(tags[0])
def = strings.TrimSpace(tags[1])
}
if name == "" {
name = field.Name
}
return name, def
}