2014-08-29 21:49:50 +04:00
|
|
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
|
|
|
// Use of this source code is governed by a MIT style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
2014-07-03 21:19:06 +04:00
|
|
|
package binding
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"encoding/xml"
|
2014-07-05 01:28:50 +04:00
|
|
|
"errors"
|
|
|
|
"net/http"
|
|
|
|
"reflect"
|
2014-07-05 04:44:32 +04:00
|
|
|
"strconv"
|
2014-07-05 01:28:50 +04:00
|
|
|
"strings"
|
2014-07-03 21:19:06 +04:00
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
|
|
|
Binding interface {
|
2014-07-05 01:28:50 +04:00
|
|
|
Bind(*http.Request, interface{}) error
|
2014-07-03 21:19:06 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// JSON binding
|
|
|
|
jsonBinding struct{}
|
|
|
|
|
2014-07-05 01:28:50 +04:00
|
|
|
// XML binding
|
2014-07-03 21:19:06 +04:00
|
|
|
xmlBinding struct{}
|
2014-07-05 01:28:50 +04:00
|
|
|
|
|
|
|
// // form binding
|
|
|
|
formBinding struct{}
|
2014-07-03 21:19:06 +04:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
JSON = jsonBinding{}
|
|
|
|
XML = xmlBinding{}
|
2014-07-05 01:28:50 +04:00
|
|
|
Form = formBinding{} // todo
|
2014-07-03 21:19:06 +04:00
|
|
|
)
|
|
|
|
|
2014-07-05 01:28:50 +04:00
|
|
|
func (_ jsonBinding) Bind(req *http.Request, obj interface{}) error {
|
|
|
|
decoder := json.NewDecoder(req.Body)
|
|
|
|
if err := decoder.Decode(obj); err == nil {
|
|
|
|
return Validate(obj)
|
|
|
|
} else {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (_ xmlBinding) Bind(req *http.Request, obj interface{}) error {
|
|
|
|
decoder := xml.NewDecoder(req.Body)
|
|
|
|
if err := decoder.Decode(obj); err == nil {
|
|
|
|
return Validate(obj)
|
|
|
|
} else {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (_ formBinding) Bind(req *http.Request, obj interface{}) error {
|
2014-07-05 04:44:32 +04:00
|
|
|
if err := req.ParseForm(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := mapForm(obj, req.Form); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return Validate(obj)
|
|
|
|
}
|
|
|
|
|
|
|
|
func mapForm(ptr interface{}, form map[string][]string) error {
|
|
|
|
typ := reflect.TypeOf(ptr).Elem()
|
|
|
|
formStruct := reflect.ValueOf(ptr).Elem()
|
|
|
|
for i := 0; i < typ.NumField(); i++ {
|
|
|
|
typeField := typ.Field(i)
|
|
|
|
if inputFieldName := typeField.Tag.Get("form"); inputFieldName != "" {
|
|
|
|
structField := formStruct.Field(i)
|
|
|
|
if !structField.CanSet() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
inputValue, exists := form[inputFieldName]
|
|
|
|
if !exists {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
numElems := len(inputValue)
|
|
|
|
if structField.Kind() == reflect.Slice && numElems > 0 {
|
|
|
|
sliceOf := structField.Type().Elem().Kind()
|
|
|
|
slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
|
|
|
|
for i := 0; i < numElems; i++ {
|
|
|
|
if err := setWithProperType(sliceOf, inputValue[i], slice.Index(i)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2014-11-11 10:43:48 +03:00
|
|
|
formStruct.Field(i).Set(slice)
|
2014-07-05 04:44:32 +04:00
|
|
|
} else {
|
|
|
|
if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-07-05 01:28:50 +04:00
|
|
|
return nil
|
2014-07-03 21:19:06 +04:00
|
|
|
}
|
|
|
|
|
2014-07-05 04:44:32 +04:00
|
|
|
func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
|
|
|
|
switch valueKind {
|
|
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
|
|
if val == "" {
|
|
|
|
val = "0"
|
|
|
|
}
|
|
|
|
intVal, err := strconv.Atoi(val)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
structField.SetInt(int64(intVal))
|
|
|
|
}
|
|
|
|
case reflect.Bool:
|
|
|
|
if val == "" {
|
|
|
|
val = "false"
|
|
|
|
}
|
|
|
|
boolVal, err := strconv.ParseBool(val)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
structField.SetBool(boolVal)
|
|
|
|
}
|
|
|
|
case reflect.Float32:
|
|
|
|
if val == "" {
|
|
|
|
val = "0.0"
|
|
|
|
}
|
|
|
|
floatVal, err := strconv.ParseFloat(val, 32)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
structField.SetFloat(floatVal)
|
|
|
|
}
|
|
|
|
case reflect.Float64:
|
|
|
|
if val == "" {
|
|
|
|
val = "0.0"
|
|
|
|
}
|
|
|
|
floatVal, err := strconv.ParseFloat(val, 64)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
structField.SetFloat(floatVal)
|
|
|
|
}
|
|
|
|
case reflect.String:
|
|
|
|
structField.SetString(val)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't pass in pointers to bind to. Can lead to bugs. See:
|
|
|
|
// https://github.com/codegangsta/martini-contrib/issues/40
|
|
|
|
// https://github.com/codegangsta/martini-contrib/pull/34#issuecomment-29683659
|
|
|
|
func ensureNotPointer(obj interface{}) {
|
|
|
|
if reflect.TypeOf(obj).Kind() == reflect.Ptr {
|
|
|
|
panic("Pointers are not accepted as binding models")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-04 14:37:22 +03:00
|
|
|
func Validate(obj interface{}, parents ...string) error {
|
2014-07-05 01:28:50 +04:00
|
|
|
typ := reflect.TypeOf(obj)
|
|
|
|
val := reflect.ValueOf(obj)
|
|
|
|
|
|
|
|
if typ.Kind() == reflect.Ptr {
|
|
|
|
typ = typ.Elem()
|
|
|
|
val = val.Elem()
|
|
|
|
}
|
|
|
|
|
2014-07-13 02:17:01 +04:00
|
|
|
switch typ.Kind() {
|
|
|
|
case reflect.Struct:
|
|
|
|
for i := 0; i < typ.NumField(); i++ {
|
|
|
|
field := typ.Field(i)
|
|
|
|
|
2014-10-07 01:52:18 +04:00
|
|
|
// Allow ignored and unexported fields in the struct
|
2015-01-01 18:22:02 +03:00
|
|
|
if len(field.PkgPath) > 0 || field.Tag.Get("form") == "-" {
|
2014-07-13 02:17:01 +04:00
|
|
|
continue
|
2014-07-05 01:28:50 +04:00
|
|
|
}
|
|
|
|
|
2014-07-13 02:17:01 +04:00
|
|
|
fieldValue := val.Field(i).Interface()
|
|
|
|
zero := reflect.Zero(field.Type).Interface()
|
|
|
|
|
|
|
|
if strings.Index(field.Tag.Get("binding"), "required") > -1 {
|
|
|
|
fieldType := field.Type.Kind()
|
|
|
|
if fieldType == reflect.Struct {
|
2015-02-04 14:37:22 +03:00
|
|
|
if reflect.DeepEqual(zero, fieldValue) {
|
|
|
|
return errors.New("Required " + field.Name)
|
|
|
|
}
|
|
|
|
err := Validate(fieldValue, field.Name)
|
2014-07-13 02:17:01 +04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else if reflect.DeepEqual(zero, fieldValue) {
|
2015-02-04 14:37:22 +03:00
|
|
|
if len(parents) > 0 {
|
|
|
|
return errors.New("Required " + field.Name + " on " + parents[0])
|
|
|
|
} else {
|
|
|
|
return errors.New("Required " + field.Name)
|
|
|
|
}
|
2014-07-13 02:17:01 +04:00
|
|
|
} else if fieldType == reflect.Slice && field.Type.Elem().Kind() == reflect.Struct {
|
|
|
|
err := Validate(fieldValue)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-07-05 01:28:50 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-07-13 02:17:01 +04:00
|
|
|
case reflect.Slice:
|
|
|
|
for i := 0; i < val.Len(); i++ {
|
|
|
|
fieldValue := val.Index(i).Interface()
|
|
|
|
err := Validate(fieldValue)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return nil
|
2014-07-05 01:28:50 +04:00
|
|
|
}
|
|
|
|
return nil
|
2014-07-03 21:19:06 +04:00
|
|
|
}
|