github.com/woremacx/kocha@v0.7.1-0.20150731103243-a5889322afc9/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/woremacx/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  }