-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathparam.go
205 lines (188 loc) · 5.11 KB
/
param.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
package kocha
import (
"database/sql"
"errors"
"fmt"
"net/url"
"path/filepath"
"reflect"
"runtime"
"strconv"
"strings"
"sync"
"time"
"github.com/naoina/kocha/util"
)
var (
ErrInvalidFormat = errors.New("invalid format")
ErrUnsupportedFieldType = errors.New("unsupported field type")
paramsPool = &sync.Pool{
New: func() interface{} {
return &Params{}
},
}
)
// ParamError indicates that a field has error.
type ParamError struct {
Name string
Err error
}
// NewParamError returns a new ParamError.
func NewParamError(name string, err error) *ParamError {
return &ParamError{
Name: name,
Err: err,
}
}
func (e *ParamError) Error() string {
return fmt.Sprintf("%v is %v", e.Name, e.Err)
}
var formTimeFormats = []string{
"2006-01-02 15:04:05",
"2006/01/02 15:04:05",
"2006-01-02T15:04:05",
"2006-01-02 15:04",
"2006/01/02 15:04",
"2006-01-02T15:04",
"2006-01-02",
"2006/01/02",
"20060102150405",
"200601021504",
"20060102",
}
// Params represents a form values.
type Params struct {
c *Context
url.Values
prefix string
}
func newParams(c *Context, values url.Values, prefix string) *Params {
p := paramsPool.Get().(*Params)
p.c = c
p.Values = values
p.prefix = prefix
return p
}
// From returns a new Params that has prefix made from given name and children.
func (params *Params) From(name string, children ...string) *Params {
return newParams(params.c, params.Values, params.prefixedName(name, children...))
}
// Bind binds form values of fieldNames to obj.
// obj must be a pointer of struct. If obj isn't a pointer of struct, it returns error.
// Note that it in the case of errors due to a form value binding error, no error is returned.
// Binding errors will set to map of returned from Controller.Errors().
func (params *Params) Bind(obj interface{}, fieldNames ...string) error {
rvalue := reflect.ValueOf(obj)
if rvalue.Kind() != reflect.Ptr {
return fmt.Errorf("kocha: Bind: first argument must be a pointer, but %v", rvalue.Type().Kind())
}
for rvalue.Kind() == reflect.Ptr {
rvalue = rvalue.Elem()
}
if rvalue.Kind() != reflect.Struct {
return fmt.Errorf("kocha: Bind: first argument must be a pointer of struct, but %T", obj)
}
rtype := rvalue.Type()
for _, name := range fieldNames {
index := params.findFieldIndex(rtype, name, nil)
if len(index) < 1 {
_, filename, line, _ := runtime.Caller(1)
params.c.App.Logger.Warnf(
"kocha: Bind: %s:%s: field name `%s' given, but %s.%s is undefined",
filepath.Base(filename), line, name, rtype.Name(), util.ToCamelCase(name))
continue
}
fname := params.prefixedName(params.prefix, name)
values, found := params.Values[fname]
if !found {
continue
}
field := rvalue.FieldByIndex(index)
for field.Kind() == reflect.Ptr {
field = field.Elem()
}
value, err := params.parse(field.Interface(), values[0])
if err != nil {
params.c.Errors[name] = append(params.c.Errors[name], NewParamError(name, err))
}
field.Set(reflect.ValueOf(value))
}
return nil
}
func (params *Params) prefixedName(prefix string, names ...string) string {
if prefix != "" {
names = append([]string{prefix}, names...)
}
return strings.Join(names, ".")
}
type embeddefFieldInfo struct {
field reflect.StructField
name string
index []int
}
func (params *Params) findFieldIndex(rtype reflect.Type, name string, index []int) []int {
var embeddedFieldInfos []*embeddefFieldInfo
for i := 0; i < rtype.NumField(); i++ {
field := rtype.Field(i)
if util.IsUnexportedField(field) {
continue
}
if field.Anonymous {
embeddedFieldInfos = append(embeddedFieldInfos, &embeddefFieldInfo{field, name, append(index, i)})
continue
}
if field.Name == util.ToCamelCase(name) {
return append(index, i)
}
}
for _, fi := range embeddedFieldInfos {
if index := params.findFieldIndex(fi.field.Type, fi.name, fi.index); len(index) > 0 {
return index
}
}
return nil
}
func (params *Params) parse(fv interface{}, vStr string) (value interface{}, err error) {
switch t := fv.(type) {
case sql.Scanner:
err = t.Scan(vStr)
case time.Time:
for _, format := range formTimeFormats {
if value, err = time.Parse(format, vStr); err == nil {
break
}
}
case string:
value = vStr
case bool:
value, err = strconv.ParseBool(vStr)
case int, int8, int16, int32, int64:
if value, err = strconv.ParseInt(vStr, 10, 0); err == nil {
value = reflect.ValueOf(value).Convert(reflect.TypeOf(t)).Interface()
}
case uint, uint8, uint16, uint32, uint64:
if value, err = strconv.ParseUint(vStr, 10, 0); err == nil {
value = reflect.ValueOf(value).Convert(reflect.TypeOf(t)).Interface()
}
case float32, float64:
if value, err = strconv.ParseFloat(vStr, 0); err == nil {
value = reflect.ValueOf(value).Convert(reflect.TypeOf(t)).Interface()
}
default:
params.c.App.Logger.Warnf("kocha: Bind: unsupported field type: %T", t)
err = ErrUnsupportedFieldType
}
if err != nil {
if err != ErrUnsupportedFieldType {
params.c.App.Logger.Warnf("kocha: Bind: %v", err)
err = ErrInvalidFormat
}
return nil, err
}
return value, nil
}
func (params *Params) reuse() {
if params != nil {
paramsPool.Put(params)
}
}