github.com/naoina/kocha@v0.7.1-0.20171129072645-78c7a531f799/param.go (about) 1 package kocha 2 3 import ( 4 "database/sql" 5 "errors" 6 "fmt" 7 "net/url" 8 "path/filepath" 9 "reflect" 10 "runtime" 11 "strconv" 12 "strings" 13 "sync" 14 "time" 15 16 "github.com/naoina/kocha/util" 17 ) 18 19 var ( 20 ErrInvalidFormat = errors.New("invalid format") 21 ErrUnsupportedFieldType = errors.New("unsupported field type") 22 23 paramsPool = &sync.Pool{ 24 New: func() interface{} { 25 return &Params{} 26 }, 27 } 28 ) 29 30 // ParamError indicates that a field has error. 31 type ParamError struct { 32 Name string 33 Err error 34 } 35 36 // NewParamError returns a new ParamError. 37 func NewParamError(name string, err error) *ParamError { 38 return &ParamError{ 39 Name: name, 40 Err: err, 41 } 42 } 43 44 func (e *ParamError) Error() string { 45 return fmt.Sprintf("%v is %v", e.Name, e.Err) 46 } 47 48 var formTimeFormats = []string{ 49 "2006-01-02 15:04:05", 50 "2006/01/02 15:04:05", 51 "2006-01-02T15:04:05", 52 "2006-01-02 15:04", 53 "2006/01/02 15:04", 54 "2006-01-02T15:04", 55 "2006-01-02", 56 "2006/01/02", 57 "20060102150405", 58 "200601021504", 59 "20060102", 60 } 61 62 // Params represents a form values. 63 type Params struct { 64 c *Context 65 url.Values 66 prefix string 67 } 68 69 func newParams(c *Context, values url.Values, prefix string) *Params { 70 p := paramsPool.Get().(*Params) 71 p.c = c 72 p.Values = values 73 p.prefix = prefix 74 return p 75 } 76 77 // From returns a new Params that has prefix made from given name and children. 78 func (params *Params) From(name string, children ...string) *Params { 79 return newParams(params.c, params.Values, params.prefixedName(name, children...)) 80 } 81 82 // Bind binds form values of fieldNames to obj. 83 // obj must be a pointer of struct. If obj isn't a pointer of struct, it returns error. 84 // Note that it in the case of errors due to a form value binding error, no error is returned. 85 // Binding errors will set to map of returned from Controller.Errors(). 86 func (params *Params) Bind(obj interface{}, fieldNames ...string) error { 87 rvalue := reflect.ValueOf(obj) 88 if rvalue.Kind() != reflect.Ptr { 89 return fmt.Errorf("kocha: Bind: first argument must be a pointer, but %v", rvalue.Type().Kind()) 90 } 91 for rvalue.Kind() == reflect.Ptr { 92 rvalue = rvalue.Elem() 93 } 94 if rvalue.Kind() != reflect.Struct { 95 return fmt.Errorf("kocha: Bind: first argument must be a pointer of struct, but %T", obj) 96 } 97 rtype := rvalue.Type() 98 for _, name := range fieldNames { 99 index := params.findFieldIndex(rtype, name, nil) 100 if len(index) < 1 { 101 _, filename, line, _ := runtime.Caller(1) 102 params.c.App.Logger.Warnf( 103 "kocha: Bind: %s:%s: field name `%s' given, but %s.%s is undefined", 104 filepath.Base(filename), line, name, rtype.Name(), util.ToCamelCase(name)) 105 continue 106 } 107 fname := params.prefixedName(params.prefix, name) 108 values, found := params.Values[fname] 109 if !found { 110 continue 111 } 112 field := rvalue.FieldByIndex(index) 113 for field.Kind() == reflect.Ptr { 114 field = field.Elem() 115 } 116 value, err := params.parse(field.Interface(), values[0]) 117 if err != nil { 118 params.c.Errors[name] = append(params.c.Errors[name], NewParamError(name, err)) 119 } 120 field.Set(reflect.ValueOf(value)) 121 } 122 return nil 123 } 124 125 func (params *Params) prefixedName(prefix string, names ...string) string { 126 if prefix != "" { 127 names = append([]string{prefix}, names...) 128 } 129 return strings.Join(names, ".") 130 } 131 132 type embeddefFieldInfo struct { 133 field reflect.StructField 134 name string 135 index []int 136 } 137 138 func (params *Params) findFieldIndex(rtype reflect.Type, name string, index []int) []int { 139 var embeddedFieldInfos []*embeddefFieldInfo 140 for i := 0; i < rtype.NumField(); i++ { 141 field := rtype.Field(i) 142 if util.IsUnexportedField(field) { 143 continue 144 } 145 if field.Anonymous { 146 embeddedFieldInfos = append(embeddedFieldInfos, &embeddefFieldInfo{field, name, append(index, i)}) 147 continue 148 } 149 if field.Name == util.ToCamelCase(name) { 150 return append(index, i) 151 } 152 } 153 for _, fi := range embeddedFieldInfos { 154 if index := params.findFieldIndex(fi.field.Type, fi.name, fi.index); len(index) > 0 { 155 return index 156 } 157 } 158 return nil 159 } 160 161 func (params *Params) parse(fv interface{}, vStr string) (value interface{}, err error) { 162 switch t := fv.(type) { 163 case sql.Scanner: 164 err = t.Scan(vStr) 165 case time.Time: 166 for _, format := range formTimeFormats { 167 if value, err = time.Parse(format, vStr); err == nil { 168 break 169 } 170 } 171 case string: 172 value = vStr 173 case bool: 174 value, err = strconv.ParseBool(vStr) 175 case int, int8, int16, int32, int64: 176 if value, err = strconv.ParseInt(vStr, 10, 0); err == nil { 177 value = reflect.ValueOf(value).Convert(reflect.TypeOf(t)).Interface() 178 } 179 case uint, uint8, uint16, uint32, uint64: 180 if value, err = strconv.ParseUint(vStr, 10, 0); err == nil { 181 value = reflect.ValueOf(value).Convert(reflect.TypeOf(t)).Interface() 182 } 183 case float32, float64: 184 if value, err = strconv.ParseFloat(vStr, 0); err == nil { 185 value = reflect.ValueOf(value).Convert(reflect.TypeOf(t)).Interface() 186 } 187 default: 188 params.c.App.Logger.Warnf("kocha: Bind: unsupported field type: %T", t) 189 err = ErrUnsupportedFieldType 190 } 191 if err != nil { 192 if err != ErrUnsupportedFieldType { 193 params.c.App.Logger.Warnf("kocha: Bind: %v", err) 194 err = ErrInvalidFormat 195 } 196 return nil, err 197 } 198 return value, nil 199 } 200 201 func (params *Params) reuse() { 202 if params != nil { 203 paramsPool.Put(params) 204 } 205 }